(STM32设计)基于机器视觉的金属件分类系统(自学习分类)


  资料下载:待更新


1、实现功能


系统采用STM32F103单片机作为控制器,通过摄像头和K210模块实现图像数据的采集,并以此实现金属键的识别,当识别后通过SG960舵机对金属键进行分类,并通过液晶进行显示,同时加入了串口通信模块,将分拣后的结果输出到PC电脑,方便用户进行管控。

可增加的功能:

主要元器件:

  STM32F103C8T6最小系统板、


2、硬件

硬件框图
  系统架构图

在这里插入图片描述

  1. STM32F103单片机:STM32F103单片机是本系统的核心控制单元,采用ARM Cortex-M3内核,具有高性能和低功耗的特点。它的主频可达到72MHz,拥有较大的内存和丰富的外设接口,适合用于嵌入式系统中的实时控制。在本系统中,STM32F103负责协调各个模块的工作,包括控制图像采集、舵机动作以及蓝牙通信。通过其GPIO接口与其他外部设备进行通信,STM32F103作为系统的大脑,有效保证了系统的实时性和稳定性。
  2. 摄像头:摄像头在本系统中用于图像数据的采集,通过高分辨率的图像传感器捕捉金属键的外观信息。摄像头传输的图像信号被STM32F103传输至K210模块进行处理和识别。其优越的成像质量和响应速度使得系统能够准确捕捉到金属键的形态特征,并为后续的分类处理提供清晰的数据输入。摄像头在实现物体检测和识别过程中起到了至关重要的作用。
  3. K210模块:K210模块是一个AI处理单元,专为图像处理和机器学习任务设计,基于RISC-V架构,具备强大的图像识别能力。在本系统中,K210用于对摄像头采集到的图像进行实时处理和分析。通过内置的深度学习算法,K210能够准确地识别金属键的形态,并生成分类结果。K210的高效处理能力显著提升了系统的识别速度和精度,为金属键分类提供了有力支持。
  4. SG960舵机:SG960舵机用于执行金属键的分类操作。它具有较高的精度和稳定性,能够在接收到控制信号后精准地调整其角度。在本系统中,当K210模块完成对金属键的识别后,STM32F103控制SG960舵机进行相应的分类动作。舵机的高扭矩和响应速度确保了金属键在分类过程中能够迅速、准确地定位到指定位置,从而完成分拣工作。
  5. 液晶显示屏:液晶显示屏(OLED12864)用于实时显示系统的工作状态和金属键的分类结果。该显示器具有清晰的显示效果和较低的功耗,在提供实时反馈的同时不会对系统的整体能耗产生过大影响。在系统运行过程中,LCD能够向操作员展示摄像头捕获的图像和K210识别的分类结果,让用户直观地了解系统当前的操作状态和结果。
    6.串口通信模块:采用CH340串口通信模块用于将分拣结果与电脑进行无线通信。通过与STM32F103的串口连接,CH340串口通信模实现了数据的TTL转USB传输,减少了开发人员的工作量。

实物

3、代码

   STM32代码采用C语言,标准库编写,软件是keil5,关键代码有中文注释,看不懂可以VX问我
以下为Maixpy IDE软件 Python语言编写的K210图像处理模块自学习处理代码:

import gc
import lcd
import sensor
import time
from maix import GPIO
from maix import KPU
from board import board_info
from fpioa_manager import fm
from image import Image

from machine import UART
fm.register(9, fm.fpioa.UART1_TX, force=True)
fm.register(10, fm.fpioa.UART1_RX, force=True)
uart = UART(UART.UART1, 115200, 8, 1, 0, timeout=1000, read_buf_len=64)
####################################################################################################################
class STATE(object):
    IDLE = 0
    INIT = 1
    TRAIN_CLASS_1 = 2
    TRAIN_CLASS_2 = 3
    TRAIN_CLASS_3 = 4
    CLASSIFY = 5
    STATE_MAX = 6


class EVENT(object):
    POWER_ON = 0            # virtual event, 用于上电初始化
    BOOT_KEY = 1            # boot键按下
    BOOT_KEY_LONG_PRESS = 2 # boot键长按约3秒
    EVENT_NEXT_MODE = 3     # virtual event, 用于切换到下一个模式
    EVENT_MAX = 4


class StateMachine(object):
    def __init__(self, state_handlers, event_handlers, transitions):
        self.previous_state = STATE.IDLE
        self.current_state = STATE.IDLE
        self.state_handlers = state_handlers
        self.event_handlers = event_handlers
        self.transitions = transitions

    def reset(self):
        '''
        重置状态机
        :return:
        '''
        self.previous_state = STATE.IDLE
        self.current_state = STATE.IDLE

    def get_next_state(self, cur_state, cur_event):
        '''
        根据当着状态和event, 从transitions表里查找出下一个状态
        :param cur_state:
        :param cur_event:
        :return:
            next_state: 下一状态
            None: 找不到对应状态
        '''
        for cur, next, event in self.transitions:
            if cur == cur_state and event == cur_event:
                return next
        return None

    # execute action before enter current state
    def enter_state_action(self, state, event):
        '''
        执行当前状态对应的进入action
        :param state: 当前状态
        :param event: 当前event
        :return:
        '''
        try:
            if self.state_handlers[state][0]:
                self.state_handlers[state][0](self, state, event)
        except Exception as e:
            print(e)

    # execute action of current state
    def execute_state_action(self, state, event):
        '''
        执行当前状态action函数
        :param state:   当前状态
        :param event:   当前event
        :return:
        '''
        try:
            if self.state_handlers[state][1]:
                self.state_handlers[state][1](self, state, event)
        except Exception as e:
            print(e)

    # execute action when exit state
    def exit_state_action(self, state, event):
        '''
        执行当前状态的退出action
        :param state: 当前状态
        :param event: 当前event
        :return:
        '''
        try:
            if self.state_handlers[state][2]:
                self.state_handlers[state][2](self, state, event)
        except Exception as e:
            print(e)

    def emit_event(self, event):
        '''
        发送event。根据当前状态和event,查找下一个状态,然后执行对应的action。
        :param event: 要发送的event
        :return:
        '''
        next_state = self.get_next_state(self.current_state, event)

        # execute enter function and exit function when state changed
        if next_state != None and next_state != self.current_state:
            self.exit_state_action(self.previous_state, event)
            self.previous_state = self.current_state
            self.current_state = next_state
            self.enter_state_action(self.current_state, event)
            print("event valid: {}, cur: {}, next: {}".format(event, self.current_state, next_state))

        # call state action for each event
        self.execute_state_action(self.current_state, event)

    def engine(self):
        '''
        状态机引擎,用于执行状态机
        :return:
        '''
        pass

def restart(self):
    '''
    重新启动状态机程序
    :return:
    '''
    global features
    self.reset()
    features.clear()
    self.emit_event(EVENT.POWER_ON)


def enter_state_idle(self, state, event):
    print("enter state: idle")


def exit_state_idle(self, state, event):
    print("exit state: idle")


def state_idle(self, state, event):
    global central_msg
    print("current state: idle")
    central_msg = None


def enter_state_init(self, state, event):
    global img_init
    print("enter state: init")
    img_init = Image(size=(lcd.width(), lcd.height()))


def exit_state_init(self, state, event):
    print("exit state: init")
    del img_init


def state_init(self, state, event):
    print("current state: init, event: {}".format(event))

    # switch to next state when boot key is pressed
    if event == EVENT.BOOT_KEY:
        self.emit_event(EVENT.EVENT_NEXT_MODE)
    elif event == EVENT.BOOT_KEY_LONG_PRESS:
        restart(self)
        return


def enter_state_train_class_1(self, state, event):
    print("enter state: train class 1")
    global train_pic_cnt, central_msg, bottom_msg
    train_pic_cnt = 0
    central_msg = "Train class 1"
    bottom_msg = "Take pictures of 1st class"


def exit_state_train_class_1(self, state, event):
    print("exit state: train class 1")


def state_train_class_1(self, state, event):
    global kpu, central_msg, bottom_msg, features, train_pic_cnt
    global state_machine
    print("current state: class 1")

    if event == EVENT.BOOT_KEY_LONG_PRESS:
        restart(self)
        return

    if train_pic_cnt == 0:  # 0 is used for prompt only
        features.append([])
        train_pic_cnt += 1
    elif train_pic_cnt <= max_train_pic:
        central_msg = None
        img = sensor.snapshot()
        feature = kpu.run_with_output(img, get_feature=True)
        features[0].append(feature)
        bottom_msg = "Class 1: #P{}".format(train_pic_cnt)
        train_pic_cnt += 1
    else:
        state_machine.emit_event(EVENT.EVENT_NEXT_MODE)


def enter_state_train_class_2(self, state, event):
    print("enter state: train class 2")
    global train_pic_cnt, central_msg, bottom_msg
    train_pic_cnt = 0
    central_msg = "Train class 2"
    bottom_msg = "Change to 2nd class please"


def exit_state_train_class_2(self, state, event):
    print("exit state: train class 2")


def state_train_class_2(self, state, event):
    global kpu, central_msg, bottom_msg, features, train_pic_cnt
    global state_machine
    print("current state: class 2")

    if event == EVENT.BOOT_KEY_LONG_PRESS:
        restart(self)
        return

    if train_pic_cnt == 0:
        features.append([])
        train_pic_cnt += 1
    elif train_pic_cnt <= max_train_pic:
        central_msg = None
        img = sensor.snapshot()
        feature = kpu.run_with_output(img, get_feature=True)
        features[1].append(feature)
        bottom_msg = "Class 2: #P{}".format(train_pic_cnt)
        train_pic_cnt += 1
    else:
        state_machine.emit_event(EVENT.EVENT_NEXT_MODE)


def enter_state_train_class_3(self, state, event):
    print("enter state: train class 3")
    global train_pic_cnt, central_msg, bottom_msg
    train_pic_cnt = 0
    central_msg = "Train class 3"
    bottom_msg = "Change to 3rd class please"


def exit_state_train_class_3(self, state, event):
    print("exit state: train class 3")


def state_train_class_3(self, state, event):
    global kpu, central_msg, bottom_msg, features, train_pic_cnt
    global state_machine
    print("current state: class 3")

    if event == EVENT.BOOT_KEY_LONG_PRESS:
        restart(self)
        return

    if train_pic_cnt == 0:
        features.append([])
        train_pic_cnt += 1
    elif train_pic_cnt <= max_train_pic:
        central_msg = None
        img = sensor.snapshot()
        feature = kpu.run_with_output(img, get_feature=True)
        features[2].append(feature)
        bottom_msg = "Class 3: #P{}".format(train_pic_cnt)
        train_pic_cnt += 1
    else:
        state_machine.emit_event(EVENT.EVENT_NEXT_MODE)


def enter_state_classify(self, state, event):
    global central_msg, bottom_msg
    print("enter state: classify")
    central_msg = "Classification"
    bottom_msg = "Training complete! Start classification"



def exit_state_classify(self, state, event):
    print("exit state: classify")


def state_classify(self, state, event):
    global central_msg, bottom_msg
    print("current state: classify, {}, {}".format(state, event))
    if event == EVENT.BOOT_KEY:
        central_msg = None
    if event == EVENT.BOOT_KEY_LONG_PRESS:
        restart(self)
        return



def event_power_on(self, value=None):
    print("emit event: power_on")


def event_press_boot_key(self, value=None):
    global state_machine
    print("emit event: boot_key")


def event_long_press_boot_key(self, value=None):
    global state_machine
    print("emit event: boot_key_long_press")


# state action table format:
#   state: [enter_state_handler, execute_state_handler, exit_state_handler]
state_handlers = {
    
    
    STATE.IDLE: [enter_state_idle, state_idle, exit_state_idle],
    STATE.INIT: [enter_state_init, state_init, exit_state_init],
    STATE.TRAIN_CLASS_1: [enter_state_train_class_1, state_train_class_1, exit_state_train_class_1],
    STATE.TRAIN_CLASS_2: [enter_state_train_class_2, state_train_class_2, exit_state_train_class_2],
    STATE.TRAIN_CLASS_3: [enter_state_train_class_3, state_train_class_3, exit_state_train_class_3],
    STATE.CLASSIFY: [enter_state_classify, state_classify, exit_state_classify]
}

# event action table, can be enabled while needed
event_handlers = {
    
    
    EVENT.POWER_ON: event_power_on,
    EVENT.BOOT_KEY: event_press_boot_key,
    EVENT.BOOT_KEY_LONG_PRESS: event_long_press_boot_key
}

# Transition table
transitions = [
    [STATE.IDLE, STATE.INIT, EVENT.POWER_ON],
    [STATE.INIT, STATE.TRAIN_CLASS_1, EVENT.EVENT_NEXT_MODE],
    [STATE.TRAIN_CLASS_1, STATE.TRAIN_CLASS_2, EVENT.EVENT_NEXT_MODE],
    [STATE.TRAIN_CLASS_2, STATE.TRAIN_CLASS_3, EVENT.EVENT_NEXT_MODE],
    [STATE.TRAIN_CLASS_3, STATE.CLASSIFY, EVENT.EVENT_NEXT_MODE]
]


####################################################################################################################
class Button(object):
    DEBOUNCE_THRESHOLD = 30  # 消抖阈值
    LONG_PRESS_THRESHOLD = 1000  # 长按阈值
    # Internal  key states
    IDLE = 0
    DEBOUNCE = 1
    SHORT_PRESS = 2
    LONG_PRESS = 3

    def __init__(self, state_machine):
        self._state = Button.IDLE
        self._key_ticks = 0
        self._pre_key_state = 1
        self.SHORT_PRESS_BUF = None
        self.st = state_machine

    def reset(self):
        self._state = Button.IDLE
        self._key_ticks = 0
        self._pre_key_state = 1
        self.SHORT_PRESS_BUF = None

    def key_up(self, delta):
        # print("up:{}".format(delta))
        # key up时,有缓存的key信息就发出去,没有的话直接复位状态
        if self.SHORT_PRESS_BUF:
            self.st.emit_event(self.SHORT_PRESS_BUF)
        self.reset()

    def key_down(self, delta):
        # print("dn:{},t:{}".format(delta, self._key_ticks))
        if self._state == Button.IDLE:
            self._key_ticks += delta
            if self._key_ticks > Button.DEBOUNCE_THRESHOLD:
                # main loop period过大时,会直接跳过去抖阶段
                self._state = Button.SHORT_PRESS
                self.SHORT_PRESS_BUF = EVENT.BOOT_KEY  # key_up 时发送
            else:
                self._state = Button.DEBOUNCE
        elif self._state == Button.DEBOUNCE:
            self._key_ticks += delta
            if self._key_ticks > Button.DEBOUNCE_THRESHOLD:
                self._state = Button.SHORT_PRESS
                self.SHORT_PRESS_BUF = EVENT.BOOT_KEY  # key_up 时发送
        elif self._state == Button.SHORT_PRESS:
            self._key_ticks += delta
            if self._key_ticks > Button.LONG_PRESS_THRESHOLD:
                self._state = Button.LONG_PRESS
                self.SHORT_PRESS_BUF = None  # 检测到长按,将之前可能存在的短按buffer清除,以防发两个key event出去
                self.st.emit_event(EVENT.BOOT_KEY_LONG_PRESS)
        elif self._state == Button.LONG_PRESS:
            self._key_ticks += delta
            # 最迟 LONG_PRESS 发出信号,再以后就忽略,不需要处理。key_up时再退出状态机。
            pass
        else:
            pass


####################################################################################################################
# 未启用 state machine 的主循环,所以这里将需要循环执行的函数放在while True里
def loop_init():
    global lcd, img_init
    if state_machine.current_state != STATE.INIT:
        return

    img_init.draw_rectangle(0, 0, lcd.width(), lcd.height(), color=(0, 0, 255), fill=True, thickness=2)
    img_init.draw_string(65, 90, "Self Learning Demo", color=(255, 255, 255), scale=2)
    img_init.draw_string(5, 210, "short press:   next", color=(255, 255, 255), scale=1)
    img_init.draw_string(5, 225, "long press:      restart", color=(255, 255, 255), scale=1)
    lcd.display(img_init)


def loop_capture():
    global central_msg, bottom_msg
    img = sensor.snapshot()
    if central_msg:
        img.draw_rectangle(0, 90, lcd.width(), 22, color=(0, 0, 255), fill=True, thickness=2)
        img.draw_string(55, 90, central_msg, color=(255, 255, 255), scale=2)
    if bottom_msg:
        img.draw_string(5, 208, bottom_msg, color=(0, 0, 255), scale=1)
    lcd.display(img)


def loop_classify():
    global central_msg, bottom_msg
    img = sensor.snapshot()

    scores = []
    feature = kpu.run_with_output(img, get_feature=True)
    high = 0
    index = 0
    for j in range(len(features)):
        for f in features[j]:
            score = kpu.feature_compare(f, feature)
            if score > high:
                high = score
                index = j
    if high > THRESHOLD:
        bottom_msg = "class:{},score:{:2.1f}".format(index + 1, high)
    else:
        bottom_msg = None

    # display info
    if central_msg:
        print("central_msg:{}".format(central_msg))
        img.draw_rectangle(0, 90, lcd.width(), 22, color=(0, 255, 0), fill=True, thickness=2)
        img.draw_string(55, 90, central_msg, color=(255, 255, 255), scale=2)
    if bottom_msg:
        uart.write("%s\r\n" % bottom_msg)
        print("bottom_msg:{}".format(bottom_msg))
        img.draw_string(5, 208, bottom_msg, color=(0, 255, 0), scale=1)
    lcd.display(img)

####################################################################################################################
# main loop
features = []
THRESHOLD = 98.5    # 比对阈值,越大越严格
train_pic_cnt = 0   # 当前分类已训练图片数量
max_train_pic = 4   # 每个类别最多训练图片数量

central_msg = None  # 屏幕中间显示的信息
bottom_msg = None   # 屏幕底部显示的信息

fm.register(board_info.BOOT_KEY, fm.fpioa.GPIOHS0)
boot_gpio = GPIO(GPIO.GPIOHS0, GPIO.IN)

lcd.init()
sensor.reset()  # Reset and initialize the sensor. It will

# run automatically, call sensor.run(0) to stop
sensor.set_pixformat(sensor.RGB565)  # Set pixel format to RGB565 (or GRAYSCALE)
sensor.set_framesize(sensor.QVGA)  # Set frame size to QVGA (320x240)
sensor.set_windowing((224, 224))
sensor.skip_frames(time=500)  # Wait for settings take effect.

lcd.rotation(2)
kpu = KPU()
print("ready load model")
#kpu.load_kmodel("/sd/mb-0.25.kmodel")
kpu.load_kmodel(0X300000,245760)
state_machine = StateMachine(state_handlers, event_handlers, transitions)
state_machine.emit_event(EVENT.POWER_ON)

btn_ticks_prev = time.ticks_ms()
boot_btn = Button(state_machine)
while True:
    gc.collect()

    # 计算boot key被压下或弹起的时间,用于消抖和长按检测, 并且向state machine发送事件
    btn_ticks_cur = time.ticks_ms()
    delta = time.ticks_diff(btn_ticks_cur, btn_ticks_prev)
    btn_ticks_prev = btn_ticks_cur
    if boot_gpio.value() == 0:
        boot_btn.key_down(delta)
    else:
        boot_btn.key_up(delta)

    # 未启用 state machine 的主循环,所以这里将需要循环执行的函数放在while True里
    if state_machine.current_state == STATE.INIT:
        loop_init()

    elif state_machine.current_state == STATE.CLASSIFY:
        loop_classify()

    elif state_machine.current_state == STATE.TRAIN_CLASS_1 or state_machine.current_state == STATE.TRAIN_CLASS_2 \
            or state_machine.current_state == STATE.TRAIN_CLASS_3:
        loop_capture()


4、原理图/PCB

  原理图和PCB都使用立创EDA绘制,对新手较为友好。可以导出为AD格式的文件

扫描二维码关注公众号,回复: 17549559 查看本文章

5、介绍

本设计计划旨在开发一套基于机器视觉的自动化分拣系统,该系统能够通过摄像头采集图像信息,利用K210图像识别模块进行数据处理和控制,以实现对不同颜色和形状模型的快速、准确分拣。以下是设计方案的主要内容和方法手段:
选择合适的摄像头和K210图像识别模块:选用低成本和高性能摄像头,确保图像采集的清晰度和稳定性。同时,选择功能强大的K210图像识别模块,它具备强大的图像处理能力和丰富的库函数,能够满足颜色和形状识别的需求。
开发颜色和形状识别算法,在K210上,开发高效的颜色和形状识别算法。通过滤波和二值化等预处理操作,提高图像质量,然后利用颜色空间转换和形态学操作等方法,实现对不同颜色和形状模型的准确识别。
将各模块集成到一个完整的系统中,进行功能测试和性能评估。要模拟实际工作环境,对系统进行严格的测试,确保其稳定性和可靠性。同时,根据测试结果调整系统参数,优化分拣速度和准确性。
在保证基本功能的基础上,还将探讨系统的扩展性。研究如何添加更多的识别特征和分拣规则,以适应不同的应用场景和需求变化。
系统架构如图1所示,系统采用STM32F103单片机作为控制器,通过摄像头和K210模块实现图像数据的采集,并以此实现金属键的识别,当识别后通过SG960舵机对金属键进行分类,并通过液晶进行显示,同时加入了串口通信模块,将分拣后的结果输出到PC电脑,方便用户进行管控。

图1 系统架构图

  1. STM32F103单片机:STM32F103单片机是本系统的核心控制单元,采用ARM Cortex-M3内核,具有高性能和低功耗的特点。它的主频可达到72MHz,拥有较大的内存和丰富的外设接口,适合用于嵌入式系统中的实时控制。在本系统中,STM32F103负责协调各个模块的工作,包括控制图像采集、舵机动作以及蓝牙通信。通过其GPIO接口与其他外部设备进行通信,STM32F103作为系统的大脑,有效保证了系统的实时性和稳定性。
  2. 摄像头:摄像头在本系统中用于图像数据的采集,通过高分辨率的图像传感器捕捉金属键的外观信息。摄像头传输的图像信号被STM32F103传输至K210模块进行处理和识别。其优越的成像质量和响应速度使得系统能够准确捕捉到金属键的形态特征,并为后续的分类处理提供清晰的数据输入。摄像头在实现物体检测和识别过程中起到了至关重要的作用。
  3. K210模块:K210模块是一个AI处理单元,专为图像处理和机器学习任务设计,基于RISC-V架构,具备强大的图像识别能力。在本系统中,K210用于对摄像头采集到的图像进行实时处理和分析。通过内置的深度学习算法,K210能够准确地识别金属键的形态,并生成分类结果。K210的高效处理能力显著提升了系统的识别速度和精度,为金属键分类提供了有力支持。
  4. SG960舵机:SG960舵机用于执行金属键的分类操作。它具有较高的精度和稳定性,能够在接收到控制信号后精准地调整其角度。在本系统中,当K210模块完成对金属键的识别后,STM32F103控制SG960舵机进行相应的分类动作。舵机的高扭矩和响应速度确保了金属键在分类过程中能够迅速、准确地定位到指定位置,从而完成分拣工作。
  5. 液晶显示屏:液晶显示屏(OLED12864)用于实时显示系统的工作状态和金属键的分类结果。该显示器具有清晰的显示效果和较低的功耗,在提供实时反馈的同时不会对系统的整体能耗产生过大影响。在系统运行过程中,LCD能够向操作员展示摄像头捕获的图像和K210识别的分类结果,让用户直观地了解系统当前的操作状态和结果。
    6.串口通信模块:采用CH340串口通信模块用于将分拣结果与电脑进行无线通信。通过与STM32F103的串口连接,CH340串口通信模实现了数据的TTL转USB传输,减少了开发人员的工作量。