Colour-Reading Glove Using APDS-9960 Sensor

by lintyl in Circuits > Raspberry Pi

271 Views, 2 Favorites, 0 Comments

Colour-Reading Glove Using APDS-9960 Sensor

Adobe Express - file.jpg

A glove designed for colour-blind individuals. It detects and translates colour values into audible tones and distinct light flashes.

Supplies

Supplies Needed:

  1. A singular glove
  2. Breadboard
  3. Raspberry Pi Pico 2W
  4. Complete code is attached
  5. APDS-9960
  6. External Speaker
  7. Neopixel Light Strip
  8. Pin-to-Pin Jumper Wires
  9. Relevant connector(s)
  10. Some cardboard scraps
  11. Some good old tape, Velcro, and nuts + bolts
  12. Micro USB Cable (must be capable of data transfer)
  13. Power Source (optional)

Import Modules and Install Packages

import time, board, pwmio, neopixel, busio
from adafruit_apds9960.apds9960 import APDS9960


Go to PyCharm Settings and under the Interpreter menu, add any necessary packages. Use the Circup terminal command to install.

Set Up Neopixel Strip

Hardware Connections:

  1. Signal to GP16
  2. Ground to G
  3. Power to 5V


CircuitPython Set Up:

# Set Up Neopixel Strip
pin = board.GP16 # Lights wired to GP16
number_of_pixels = 10
brightness = 0.5
pixels = neopixel.NeoPixel(pin, number_of_pixels, brightness = brightness)


# Test Out Your Lights
# print("Testing out the Lights")
# pixels.fill((0, 0, 255))
# while True:
# pass

Set Up External Speaker

Hardware Connections:

  1. Signal to GP0
  2. Ground to G
  3. Power to 3V


CircuitPython Set Up:

# Set Up Speaker
audio = board.GP0 # Audio wired to GP0
volume = 0.05 # Adjust volume as needed
buzzer = pwmio.PWMOut(
audio,
duty_cycle = 0,
frequency = 440,
variable_frequency = True
)

Set Up APDS-9960

3595-06.jpg

Hardware Connections:

  1. SDA to GP4
  2. SCL to GP5
  3. Ground to G
  4. Power to 3V


CircuitPython Set Up:

# Set Up APDS-9960
i2c = busio.I2C(board.GP5, board.GP4) # Pico: SCL = GP5, SDA = GP4
sensor = APDS9960(i2c)
sensor.enable_color = True
time.sleep(0.2)

Set Up Definitions

Set up the 'play_tone()' function that makes your external speaker play a tone (beep) at a specific frequency (pitch) for a specific duration (length of time).


def play_tone(freq, duration):
global volume
if freq <= 0:
time.sleep(duration)
return
buzzer.frequency = freq
buzzer.duty_cycle = int(2**15 * volume)
time.sleep(duration)
buzzer.duty_cycle = 0 # Off


Set up the 'clear_pixels()' function that turns off all LEDs on your Neopixel strip.

def clear_pixels():
pixels.fill((0, 0, 0))
pixels.show()


Set up the 'speak()' function that looks up a tone based on the detected label (ie. “light” or “red-ish”) and plays the corresponding beep sound for feedback.

def speak(label):
# print(label)
tones = {
"light": 800,
"dark": 200,
"high contrast": 600,
"low contrast": 300,
"red-ish": 660,
"green-ish": 550,
"blue-ish": 440,
"yellow-ish": 770,
"neutral": 500
}
play_tone(tones.get(label, 500), 0.2)


Set up the 'brightness_pattern()' function that uses different NeoPixel light animations (fast white flashes or slow blue pulses) to visually show whether the scanned object is classified as “light” or “dark.”

def brightness_pattern(brightness_label):
clear_pixels()

if brightness_label == "light":
# Fast white flashes
for _ in range(3):
pixels.brightness = 0.7
pixels.fill((255, 255, 255))
pixels.show()
time.sleep(0.1)
clear_pixels()
time.sleep(0.1)
pixels.brightness = brightness

else: # "Dark"
# Slow soft blue breathing
for b in (0.05, 0.2, 0.05):
pixels.brightness = b
pixels.fill((0, 0, 80))
pixels.show()
time.sleep(0.25)
pixels.brightness = brightness
clear_pixels()


Set up the 'contrast_pattern()' function that displays a bold flashing pattern for high contrast or a soft steady glow for low contrast to visually indicate the contrast level detected by the sensor.

def contrast_pattern(contrast_label):
clear_pixels()

if contrast_label == "high contrast":
# Alternating bright/dark
for _ in range(3):
for i in range(number_of_pixels):
pixels[i] = (255, 255, 255) if i % 2 == 0 else (0, 0, 0)
pixels.show()
time.sleep(0.12)
clear_pixels()
time.sleep(0.12)

else: # "Low Contrast"
# Soft glow
pixels.fill((40, 40, 40))
pixels.show()
time.sleep(0.6)
clear_pixels()


Set up the 'color_family_pattern()' function that lights the NeoPixel strip in a solid color matching the detected color family (like red-ish or blue-ish) to give clear visual feedback.

def color_family_pattern(color_family):
color_map = {
"red-ish": (255, 0, 0),
"green-ish": (0, 180, 0),
"blue-ish": (0, 0, 255),
"yellow-ish": (255, 200, 0),
"neutral": (80, 80, 80)
}
color = color_map.get(color_family, (80, 80, 80))

pixels.brightness = brightness
pixels.fill(color)
pixels.show()
time.sleep(0.5)
clear_pixels()

Define Colour Groupings

The program compares the red (R), green (G), and blue (B) values from the APDS-9960 sensor to determine which color family the scanned object belongs to.

  1. Check for invalid or initialization readings: If both R and G are zero (common during sensor warm-up), the code forces the reading to “neutral” so the system does not accidentally classify garbage data as a color.
if (r + g) == 0:
rg_diff_ratio = 1.0
  1. Calculate how similar Red (R) and Green (G) are: A 0 means R and G are almost identical. Higher numbers mean that they differ a lot. This helps distinguish yellow (where R and G are similar) from red or green.
else:
# Measure how similar R and G are (0 = identical, 1 = completely different)
rg_diff_ratio = abs(r - g) / (r + g)
  1. Classify Yellow (Y) first: Yellow is defined as where R and G are both strong, R and G are relatively similar, and both are much stronger than B.
# YELLOW: R and G both strong, similar, and much stronger than B
if (r + g) > 2 * b and rg_diff_ratio < 0.21:
color_family = "yellow-ish"
  1. Classify the primary colours next: Colour is defined as 'Red-ish' if R > G and R > B, 'Green-ish' if G > R and G > B, and 'Blue-ish' if B > R and B > G.
# RED: R clearly dominant
elif r > g and r > b:
color_family = "red-ish"
# GREEN: G clearly dominant
elif g > r and g > b:
color_family = "green-ish"
# BLUE: B clearly dominant
elif b > r and b > g:
color_family = "blue-ish"
  1. Classify everything else as Neutral: If none of the above conditions match (e.g., grays, browns, blacks, low saturation colors), then the result is 'neutral'.
else:
color_family = "neutral"



Full Code for Colour Groupings Below:

# If the sensor hasn't stabilised or returns zeros, default to neutral
if (r + g) == 0:
rg_diff_ratio = 1.0 # high difference → ensures NOT yellow
else:
# Measure how similar R and G are (0 = identical, 1 = completely different)
rg_diff_ratio = abs(r - g) / (r + g)

# YELLOW: R and G both strong, similar, and much stronger than B
if (r + g) > 2 * b and rg_diff_ratio < 0.21:
color_family = "yellow-ish"
# RED: R clearly dominant
elif r > g and r > b:
color_family = "red-ish"
# GREEN: G clearly dominant
elif g > r and g > b:
color_family = "green-ish"
# BLUE: B clearly dominant
elif b > r and b > g:
color_family = "blue-ish"
else:
color_family = "neutral"

Executing and Testing

This section prints the sensor readings for debugging, plays the corresponding audio tones for brightness/contrast/colour, and shows matching NeoPixel light patterns before pausing briefly for the next scan.

Run the code to test and calibrate colour groups if needed.


# Debug Print
print("Raw:", r, g, b, c)
print(f"Object is {brightness_label}, {contrast_label}, {color_family} colour family.\n")

# Audio
speak(brightness_label)
speak(contrast_label)
speak(color_family)

# NeoPixel visuals
brightness_pattern(brightness_label)
contrast_pattern(contrast_label)
color_family_pattern(color_family)

time.sleep(0.5)

Putting the Hardware Together

IMG_4034.JPG
IMG_4036.JPG
IMG_4037.JPG
IMG_4038.jpg

Gather all your hardware and material components and get ready to put everything together!

  1. Use the Velcro to mount the breadboard and external speaker to the back side of the glove. Secure with some tape or additional Velcro if needed.
  2. Lay flat and mount the Neopixel light strip to a cardboard scrap using Velcro. Secure with some tape or additional Velcro if needed.
  3. Position the APDS-9960 sensor on the palm side of the glove and use nuts and bolts to secure it tightly.
  4. Connect the Raspberry Pi Pico 2W to a power source and run your code!

Enjoy and Re-Calibrate More If Necessary

Here's a video demo attached of how the glove should work! Re-calibrate your classification groupings as needed for your samples.


Additional Enhancement to Consider:

  1. Record audio that says "Light", "Dark", "High Contrast", "Low Contrast", "Red-ish", "Green-ish", "Blue-ish", and "Yellow-ish", and play it in place of the tones. You may record these audio files and convert them to .wav files using Audacity.
  2. Build a housing unit for the hardware components that are mounted on the back of the glove. Think of better wire management techniques so it doesn't look like you have a bomb strapped to your hand!