PERCH ARNEL II MONTEFALCON
Published © MIT

RT-Thread - Vision Board Basic Color Sorting w/ Arduino

Smart Color-Tracking Actuator that bridges the gap between high-speed computer vision and physical robotics. Using an OpenMV Cam & Arduino

BeginnerShowcase (no instructions)3 hours13
RT-Thread - Vision Board Basic Color Sorting w/ Arduino

Things used in this project

Hardware components

RT-Thread Vision Board
×1
Arduino Nano
×1
SG90 Micro-servo motor
SG90 Micro-servo motor
×1

Software apps and online services

Arduino IDE
Arduino IDE
OpenMV IDE

Story

Read more

Schematics

Circuit Diagram

Just follow the circuit diagram.

Code

OpenMV Code for Vision Board

Python
Paste it on OpenMV IDE and connect the vision board then run
import sensor, image, time, math
from pyb import UART

# --- UART SETUP ---
uart = UART(2, 9600, timeout_char=200)

# --- SENSOR SETUP ---
sensor.reset()
sensor.set_pixformat(sensor.RGB565)
sensor.set_framesize(sensor.QVGA)
sensor.set_windowing((240, 240))

# --- 180 DEGREE ROTATION ---
sensor.set_vflip(True)
sensor.set_hmirror(True)
# ---------------------------

sensor.skip_frames(time=2000)

# --- COLOR THRESHOLDS ---
thresholds = [
    (0, 100, 56, 95, 41, 74),       # 0: Red-ish
    (0, 100, -128, -22, -128, 99),  # 1: Green-ish
    (0, 100, -128, 98, -128, -16)   # 2: Blue-ish
]

# --- HUE CALCULATION ---
def get_hue(r, g, b):
    r, g, b = r/255.0, g/255.0, b/255.0
    mx = max(r, g, b)
    mn = min(r, g, b)
    df = mx - mn
    if mx == mn:
        h = 0
    elif mx == r:
        h = (60 * ((g - b) / df) + 360) % 360
    elif mx == g:
        h = (60 * ((b - r) / df) + 120) % 360
    elif mx == b:
        h = (60 * ((r - g) / df) + 240) % 360
    return int(h)

clock = time.clock()

# --- STATE VARIABLE ---
# Stores the last color sent to the Arduino
last_sent_color = "None"

while(True):
    clock.tick()
    img = sensor.snapshot()

    # Find blobs
    blobs = img.find_blobs(thresholds, pixels_threshold=200, area_threshold=200, merge=True)

    current_color = "None" # Default if nothing is found
    largest_blob = None

    if blobs:
        # 1. Find the Largest Blob (Ignores small noise)
        largest_blob = max(blobs, key=lambda b: b.pixels())

        # 2. Get Color Stats
        stats = img.get_statistics(roi=largest_blob.rect())
        rgb_tuple = image.lab_to_rgb((stats.l_mean(), stats.a_mean(), stats.b_mean()))
        hue = get_hue(rgb_tuple[0], rgb_tuple[1], rgb_tuple[2])

        # 3. Determine Color Name
        if 330 <= hue or hue < 25:
            current_color = "Red"
        elif 25 <= hue < 85:
            current_color = "Yellow"
        elif 85 <= hue < 160:
            current_color = "Green"
        elif 160 <= hue < 260:
            current_color = "Blue"
        elif 260 <= hue < 330:
            current_color = "Violet"
        else:
            current_color = "Unknown"

    # --- INSTANT SEND LOGIC ---
    # If the color we see NOW is different from the LAST one we sent...
    if current_color != last_sent_color:

        # Don't send "None" or "Unknown" to Arduino, just reset the state
        if current_color != "None" and current_color != "Unknown":
            uart.write(current_color + "\n")
            print("UART Sent:", current_color)

        # Update the state immediately
        last_sent_color = current_color


    # --- DRAWING ---
    if largest_blob and current_color != "None":
        img.draw_rectangle(largest_blob.rect(), color=(255, 255, 255), thickness=3)
        img.draw_string(largest_blob.x(), largest_blob.y() - 15, current_color, color=(255, 255, 255))

    print(clock.fps(), "fps")

Arduino Code for communicating OpenMV board and SG90 servo

C/C++
Make sure that the you follow the wiring diagram provided. Set the baud rate to 9600.
#include <SoftwareSerial.h>
#include <Servo.h>

// --- CONFIGURATION ---
// CHANGED: Use 9600 Baud for stability with SoftwareSerial
SoftwareSerial camSerial(2, 3); 

Servo myServo;
const int SERVO_PIN = 9; 

const int CENTER_POS = 75; 
const int RED_POS = 40;
const int BLUE_POS = 110;

String lastColor = "";

void setup() {
  // CHANGED: Lowered to 9600
  Serial.begin(9600);
  camSerial.begin(9600); 

  myServo.attach(SERVO_PIN);
  
  Serial.println("--- Initialization ---");
  myServo.write(RED_POS);
  delay(500); // Reduced delay for faster startup
  myServo.write(BLUE_POS);
  delay(500); 
  myServo.write(CENTER_POS); 
  delay(500);

  Serial.println("Ready.");
}

void loop() {
  if (camSerial.available() > 0) {
    
    // --- FIX: READ THE FRESHEST DATA ---
    // If the camera sent multiple messages while we were moving the servo,
    // the buffer might contain: "Red\nRed\nBlue\n"
    // We want the LAST one, not the first one.
    
    String colorName = camSerial.readStringUntil('\n');
    colorName.trim(); 
    
    // Quick loop to clear any remaining queued messages
    // This ensures we don't react to old history
    while(camSerial.available() > 0) {
       char t = camSerial.read(); 
    }

    if (colorName.length() > 0) {
      
      if (colorName != lastColor) {
        
        Serial.print("New: ");
        Serial.println(colorName);

        // 1. Reset to Center
        myServo.write(CENTER_POS);
        delay(300); // Reduced slightly for snappiness

        // 2. Determine Target
        int targetAngle = -1; 
        
        if (colorName == "Red") targetAngle = RED_POS;
        else if (colorName == "Blue") targetAngle = BLUE_POS;

        // 3. Action
        if (targetAngle != -1) {
           Serial.print("Moving -> ");
           Serial.println(targetAngle);
           myServo.write(targetAngle);
           
           // Waiting time (Action Duration)
           delay(1000);
           
           // Return to Center
           myServo.write(CENTER_POS);
        }

        lastColor = colorName;
      }
    }
  }
}

Credits

PERCH ARNEL II MONTEFALCON
1 project • 0 followers

Comments