Created
September 23, 2025 15:41
-
-
Save greg-kennedy/b6d365f22d5cfa03fdbbb7946bba8e12 to your computer and use it in GitHub Desktop.
Image classifier in Processing, based on example from "The Hitch-Hiker's Guide to Artificial Intelligence" (1986)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /** | |
| * Image Classifier (Perceptron) | |
| * | |
| * Based on the "Computer Vision" example from | |
| * "The Hitch-Hiker's Guide to Artificial Intelligence" (1986) | |
| * Richard Forsyth, Chris Naylor | |
| * | |
| * https://archive.org/details/forsyth-naylor-hitchhikers-guide-to-ai-pc-basic/page/94/mode/2up | |
| * https://en.wikipedia.org/wiki/Perceptron | |
| */ | |
| // the image canvas - 16x16 as a linear 1-bit collection | |
| boolean [] s = new boolean[256]; | |
| // "Association" layer - randomized, fixed connections to S units | |
| // there are 32 cells each connected to 8 inputs | |
| boolean [][][] a = new boolean[2][32][2 << 8]; | |
| // Summation of all firing A units per class (2 classes) | |
| int c[] = new int[2]; | |
| // UI - button color | |
| color t0, t1, clear; | |
| // trains count | |
| int t0_count = 0, t1_count = 0; | |
| void setup() { | |
| size(512, 384); | |
| t0 = color(128, 0, 0); | |
| t1 = color(0, 128, 0); | |
| clear = color(0, 0, 128); | |
| textSize(16); | |
| noLoop(); | |
| } | |
| void draw() { | |
| background(192); | |
| // draw area | |
| for (int y = 0; y < 16; y ++) { | |
| for (int x = 0; x < 16; x ++) { | |
| fill(s[y * 16 + x] ? 255 : 0); | |
| rect(x * 24, y * 24, 24, 24); | |
| } | |
| } | |
| // buttons | |
| fill(t0); | |
| rect(416, 32, 32, 64); | |
| fill(t1); | |
| rect(448, 32, 32, 64); | |
| fill(clear); | |
| rect(416, 288, 64, 64); | |
| // button labels | |
| fill(255); | |
| text("Train", 432, 48); | |
| text("A", 424, 64); | |
| text(t0_count, 424, 80); | |
| text("B", 456, 64); | |
| text(t1_count, 456, 80); | |
| text("Clear", 432, 304); | |
| // text elements | |
| fill(0); | |
| // How closely our sample image matches the two classes | |
| text("C(A): " + c[0] / 32.0, 400, 160); | |
| text("C(B): " + c[1] / 32.0, 400, 192); | |
| // Theoretically, whichever scores higher is a closer match... | |
| text("Result: " + (c[0] > c[1] ? "A" : "B"), 400, 224); | |
| } | |
| // "Trains" an image into the chosen class | |
| // Each middle-layer cell is connected to 8 sensors (pixels) in a random but fixed order | |
| void train(int c) | |
| { | |
| randomSeed(0); | |
| // 32 passes of 8 random pixel pickings | |
| for (int r = 0; r < 32; r ++) { | |
| // For each cell we have essentially a LUT that returns "true" or "false", | |
| // based on the bit pattern created from the 8 inputs. | |
| // Create that pattern, then mark TRUE for this CLASS when this pattern is seen | |
| // Multiple trainings fill out the LUTs with variations and theoretically improve detection | |
| int addr = 0; | |
| for (int i = 0; i < 8; i ++) { | |
| int pos = int(random(256)); | |
| if (s[pos]) | |
| addr |= 1 << i; | |
| } | |
| a[c][r][addr] = true; | |
| } | |
| } | |
| // Checks an image against the (two) classes and returns the similarity to each | |
| void classify() | |
| { | |
| c[0] = c[1] = 0; | |
| randomSeed(0); | |
| // Same 32 passes of 8 random pixel pickings | |
| for (int r = 0; r < 32; r ++) { | |
| int addr = 0; | |
| for (int i = 0; i < 8; i ++) { | |
| int pos = int(random(256)); | |
| if (s[pos]) | |
| addr |= 1 << i; | |
| } | |
| // As before, for each cell, check the bit pattern it's "seeing" against what we've trained in the table | |
| // check each of its two LUTs to determine whether it "fires" or not, per-class | |
| if (a[0][r][addr]) c[0] ++; | |
| if (a[1][r][addr]) c[1] ++; | |
| } | |
| } | |
| // drawing w/ left or right mouse button | |
| void mouseDragged() { | |
| if (mouseX < 384 && mouseY < 384) { | |
| int i = (mouseY / 24) * 16 + (mouseX / 24); | |
| if (! s[i] && mouseButton == LEFT) { | |
| s[i] = true; | |
| redraw(); | |
| } | |
| else if (s[i] && mouseButton == RIGHT) { | |
| s[i] = false; | |
| redraw(); | |
| } | |
| } else { | |
| mouseMoved(); | |
| } | |
| } | |
| void mouseMoved() { | |
| color oldT0 = t0, oldT1 = t1, oldClear = clear; | |
| if (mouseX > 416 && mouseY > 32 && mouseX < 416 + 64 && mouseY < 32 + 64) { | |
| if (mouseX < 448) { | |
| t0 = color(255, 0, 0); | |
| t1 = color(0, 128, 0); | |
| } else { | |
| t0 = color(128, 0, 0); | |
| t1 = color(0, 255, 0); | |
| } | |
| clear = color(0, 0, 128); | |
| } else if (mouseX > 416 && mouseY > 288 && mouseX < 416 + 64 && mouseY < 288 + 64) { | |
| t0 = color(128, 0, 0); | |
| t1 = color(0, 128, 0); | |
| clear = color(0, 0, 255); | |
| } else { | |
| t0 = color(128, 0, 0); | |
| t1 = color(0, 128, 0); | |
| clear = color(0, 0, 128); | |
| } | |
| if (oldT0 != t0 || oldT1 != t1 || oldClear != clear) redraw(); | |
| } | |
| // clicking on one of the buttons | |
| void mouseReleased() { | |
| if (mouseX > 416 && mouseY > 32 && mouseX < 416 + 64 && mouseY < 32 + 64) { | |
| if (mouseX < 448) { | |
| t0 = color(255); | |
| t0_count ++; | |
| train(0); | |
| } else { | |
| t1 = color(255); | |
| t1_count ++; | |
| train(1); | |
| } | |
| } else if (mouseX > 416 && mouseY > 288 && mouseX < 416 + 64 && mouseY < 288 + 64) { | |
| clear = color(255); | |
| for (int y = 0; y < 16; y ++) { | |
| for (int x = 0; x < 16; x ++) { | |
| s[y * 16 + x] = false; | |
| } | |
| } | |
| } | |
| classify(); | |
| redraw(); | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment