Skip to content

Instantly share code, notes, and snippets.

@greg-kennedy
Created September 23, 2025 15:41
Show Gist options
  • Save greg-kennedy/b6d365f22d5cfa03fdbbb7946bba8e12 to your computer and use it in GitHub Desktop.
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)
/**
* 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