Hackster is hosting Hackster Holidays, Ep. 7: Livestream & Giveaway Drawing. Watch previous episodes or stream live on Friday!Stream Hackster Holidays, Ep. 7 on Friday!
Gene
Published

Automatic detection system for cat food bowls based on machi

Detect the food in the cat food bowl using machine vision without the need for any machine learning models.

BeginnerFull instructions provided1 hour1,084
Automatic detection system for cat food bowls based on machi

Things used in this project

Hardware components

Raspberry Pi 5
Raspberry Pi 5
×1
Seeed Studio XIAO ESP32S3 Sense
Seeed Studio XIAO ESP32S3 Sense
×1

Software apps and online services

Arduino IDE
Arduino IDE

Story

Read more

Code

Circuitpython Code

Python
import board
import busio
import espcamera
import ulab.numpy as np
import time
import gc



def init_cam():
    _i2c = busio.I2C(scl=board.IO39, sda=board.IO40)
    _data_pin = [
        board.IO15,
        board.IO17,
        board.IO18,
        board.IO16,
        board.IO14,
        board.IO12,
        board.IO11,
        board.IO48
    ]
    _cam = espcamera.Camera(
        data_pins=_data_pin,
        pixel_clock_pin=board.IO13,
        vsync_pin=board.IO38,
        href_pin=board.IO47,
        i2c=_i2c,
        external_clock_pin=board.IO10,
        # external_clock_frequency=20_000_000,
        # powerdown_pin=None,
        # reset_pin=None,
        pixel_format=espcamera.PixelFormat.RGB565,
        # frame_size=espcamera.FrameSize.SVGA,
        # jpeg_quality = 5,
        framebuffer_count = 1,
        # grab_mode=espcamera.GrabMode.WHEN_EMPTY
    )
    _cam.vflip = False
    _cam.hmirror = True
    return _cam


def color(rgb565):
    high = rgb565 >> 8
    low = rgb565 & 0xFF
    rgb565 = (low << 8) | high
    R5 = rgb565 >> 11
    G6 = (rgb565 >> 5) & 0B111111
    B5 = rgb565 & 0B11111
    R8 = ( R5 * 527 + 23 ) >> 6
    G8 = ( G6 * 259 + 33 ) >> 6
    B8 = ( B5 * 527 + 23 ) >> 6
    RGB8 = [R8, G8, B8]
    return RGB8

def get_array(bitmap):
    image_array = np.zeros((bitmap.height*bitmap.width, 3), dtype=np.uint8)
    for y in range(bitmap.height):
        for x in range(bitmap.width):
            image_array[x*bitmap.height+y] = color(bitmap[x,y])
        print("\r" + "progress: " + str(int(y * 100 / (bitmap.height - 1))) + "%" + " " * 3, end="")
    print("")
    return image_array

def calculate(image_array, target_color, tolerance):
    # Calculate vector norm
    color_diff = np.linalg.norm(image_array - target_color, axis=1)
    # Calculate the number of similar pixels
    similar_pixels = np.sum(color_diff < tolerance)
    # Calculate the proportion
    total_pixels = image_array.shape[0]
    proportion = similar_pixels / total_pixels
    return proportion


def get_params(image_array):
    avg_color = np.mean(image_array, axis=0)
    threshold = np.linalg.norm(np.std(image_array, axis=0))
    gc.collect()
    print(avg_color, threshold)


cam = init_cam()

while True:
    cur_time = time.time()
    img_array = get_array(cam.take(1))
    get_params(img_array)
    gc.collect()
    proportion = calculate(img_array, [113, 117, 113], 100)
    print("Color Proportion: ", proportion)
    print("Time(s): ", time.time() - cur_time)
    del img_array
    gc.collect()

RPI Code

Python
from PIL import Image
import io
import requests
import numpy as np
import time
import smtplib
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText


def find_color_proportion(image_array, target_color, tolerance):
    # Calculate vector norm
    color_diff = np.linalg.norm(image_array - target_color, axis=2)
    # Calculate the number of similar pixels
    similar_pixels = np.sum(color_diff < tolerance)
    # Calculate the proportion
    total_pixels = image_array.shape[0] * image_array.shape[1]
    proportion = similar_pixels / total_pixels
    return proportion

def get_params(image_array):
    avg_color = np.mean(image_array, axis=(0, 1))
    std_color = np.std(image_array, axis=(0, 1))
    threshold = np.linalg.norm(std_color)
    print(avg_color, threshold)

def send_mail(msg):
    content = MIMEMultipart()
    content["subject"] = "cat food"
    content["from"] = sender
    content["to"] = receiver
    content.attach(MIMEText(msg))
    with smtplib.SMTP_SSL(host=host, port=port) as smtp:
        try:
            smtp.login(sender, app_password)
            smtp.send_message(content)
            print("Email Sent!")
        except Exception as error:
            print("Error: ", error)





host = "smtp.qq.com"
port = 465
sender = "xxxxxxxxxxxxxxx@qq.com"
app_password = "xxxxxxxxxxxxxxxxx"
receiver = sender



while True:
    image_array = np.array(Image.open(io.BytesIO(requests.get("http://192.168.1.91/capture?_cb=0").content)))
    # get_params(image_array)
    proportion = find_color_proportion(image_array, [25, 29, 21], 30)
    print("Color Proportion:", proportion)
    if proportion < 0.20:
        send_mail("Cat Food Bowl is EMPTY.")
    time.sleep(10)

settings.toml

Toml
CIRCUITPY_RESERVED_PSRAM=1000000

Credits

Gene

Gene

1 project • 1 follower

Comments