OpenMv物体识别串口传输到STM32通过LCD屏显示识别结果(附源码)

1:实验项目概括:

本实验是首先通过openmv采集图片信息,然后在openmv上进行物体识别并处理结果,接着通过openmv和stm32串口通信,将识别结果传输到stm32上,由stm32控制lcd屏幕进行结果显示。

2:实验硬件: 

正点原子stm32精英开发板、3.5寸TFTLCD屏、STLINK烧录器、openmv模块、 杜邦线若干、usb数据线等。 

3:实验软件:

keil5、OpenMVIDE

4:实验过程:

一:OpenMV代码

第一步是数据集的采集,利用openmv可插SD卡进行储存数据的特性,参考openmv官方人脸识别实验(分辨不同人脸 · OpenMV中文入门教程),我们可进行相应修改,这里我们根据人脸识别原理,进行不同物体识别,在本实验中我们进行的是对手机、人、书本三类不同物体的识别。

(1)创建存放文件夹

首先是openmv的拍摄代码,这部分代码的作用是用openmv拍摄这三类数据集若干样片储存在SD卡上,需要在运行代码上需要在SD卡上创建如截图所示文件夹:object/sample1、2、3,在object里创建需要存储不同数据集的文件夹,本实验只采集三类,读者可根据需要自行添加或删除。

(2)运行拍摄代码进行数据集采集

这里的代码需要注意的是每次采集需要修改num的值,每次采集一种物体时修改num的值,例如: 我要把手机的对象数据集拍摄储存到object/sample1文件里,需要修改为num = 1 ,书本的对象数据拍摄存储到object/sample2,需要修改为num=2,依次类推,每次修改代码运行后只能拍摄一种对象数据集。

import sensor, image, machine, time
from machine import Pin

# 初始化摄像头传感器
sensor.reset()
sensor.set_pixformat(sensor.GRAYSCALE)
sensor.set_framesize(sensor.B128X128)
sensor.set_windowing((92, 112))
sensor.skip_frames(time=2000)

# 初始化按键和LED
KEY = Pin('P9', Pin.IN, Pin.PULL_UP)
red_led = machine.Pin('LED_RED', machine.Pin.OUT)
blue_led = machine.Pin('LED_BLUE', machine.Pin.OUT)

num = 1  # 设置拍摄对象序号,这里特别注意,每次拍摄时需要进行修改num的值,根据创建的文件夹sample的数量,相应更改数值
n = 10   # 设置每个物体拍摄图片的数量

# 拍摄函数
def take_snapshot():
    red_led.on()
    time.sleep(0.1)  # 等待防抖动

    # 红灯灭,蓝灯亮
    red_led.off()
    blue_led.on()

    global n
    print("Taking snapshot %d" % n)
    img = sensor.snapshot()
    img.save("object/sample%s/%s.pgm" % (num, n)) # 存放的路径在这里
    n -= 1

    blue_led.off()
    print("Snapshot taken! Reset the camera to see the saved image.")

# 主循环
while n > 0:
    if KEY.value() == 0:  # 按键被按下接地
        take_snapshot()
        time.sleep(0.5)  # 等待一段时间,避免连续拍摄

    else:
        img = sensor.snapshot()

    time.sleep(0.1)  # 小延迟,避免过快检测按键

运行结果如下示例,当我num=1时(即第一个文件夹),n=10时(拍摄10张),会存储到SD卡上这个文件上,我拍摄的是手机数据集,每次按下按键会拍摄一张手机的样片,按10次拍摄10张结束拍摄。

运行结果如下,这里存放的是手机的样片,由于储存的是pgm图片格式,可以用ACDSee、PS等软件查看。每次要reset一下openmv,才能在CD卡上查看拍摄结果。

然后接下来拍摄人、书本的样片,只需要修改代码的num的值为2,过程同上。

(3)运行识别代码进行物体识别

这里的代码是开启摄像头进行对实时物体的识别,然后将识别结果数据打包串口传输准备发送出去,openmv串口参考(串口通信上 · OpenMV中文入门教程

import sensor, time, image,machine
from pyb import UART
from machine import Pin

sensor.reset() # 初始化摄像头传感器
sensor.set_pixformat(sensor.GRAYSCALE) # 设置图像格式为灰度图
sensor.set_framesize(sensor.B128X128) # 设置图像尺寸
sensor.set_windowing((92, 112)) # 设置窗口大小
sensor.skip_frames(10) # 让新设置生效
sensor.skip_frames(time = 2000) # 等待5秒

NUM_SUBJECTS = 3 # 图像库中不同种类,一共3种
NUM_SUBJECTS_IMGS = 10 # 每种类有10张样本图片

# 初始化按键和串口
KEY = Pin('P9', Pin.IN, Pin.PULL_UP)
uart = UART(3, 115200)
red_led = machine.Pin('LED_RED', machine.Pin.OUT)
blue_led = machine.Pin('LED_BLUE', machine.Pin.OUT)

def object_detection():
    # 红灯灭,蓝灯亮

    blue_led.on()
    global pmin, num

    # 重置 pmin 和 num
    pmin = 999999
    num = 0

    # 拍摄当前人脸
    img = sensor.snapshot()
    d0 = img.find_lbp((0, 0, img.width(), img.height()))

    for s in range(1, NUM_SUBJECTS + 1):
        dist = 0
        for i in range(2, NUM_SUBJECTS_IMGS + 1):
            img = image.Image("object/sample%s/%s.pgm" % (s, i))
            d1 = img.find_lbp((0, 0, img.width(), img.height()))
            # d1为第s文件夹中的第i张图片的lbp特征
            dist += image.match_descriptor(d0, d1) # 计算d0 d1即样本图像与被检测人脸的特征差异度
        avg_dist = dist / NUM_SUBJECTS_IMGS
        print("Average dist for subject %d: %d" % (s, avg_dist))
        if avg_dist < pmin:
            pmin = avg_dist
            num = s
        print('pmin:', pmin)

     打印识别结果
    if num == 1:
        print('Phone') # num为当前最匹配的物体编号
    elif num == 2:
        print('People')
    else:
        print('Book')

    # 实例化数据
    output=bytearray([0xA5,num,0XA6])

    # 传输数据
    uart.write(output)
    blue_led.off()
    print(output)

# 按键按下检测并传输数据
while True:
    if KEY.value() == 0:  # 按键被按下接地
        object_detection()
        time.sleep(0.5)  # 等待一段时间,避免连续拍摄
    else:
        img = sensor.snapshot()

    time.sleep(0.1)  # 小延迟,避免过快检测按键



代码运行如下,这里识别的对象的人,按下按键KEY进行拍摄

识别结果显示为People

然后我们在试试识别手机

识别正确,识别结果显示为Phone

二:Stm32代码

接下来是stm32代码部分,主要是设置串口通信接收来自OpenMV发送结果,然后进行数据处理在LCD屏上显示识别结果。

stm32,main主函数代码

#include "delay.h"
#include "sys.h"
#include "usart.h"
#include "uart4.h" 
#include "openmv.h"
#include "lcd.h"

int x=0;
int y=0;
uint8_t rx_data=0; // 存储接收到的串口数据、
 int main(void)
 {		
	delay_init();	    	 //延时函数初始化	  
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //设置NVIC中断分组2:2位抢占优先级,2位响应优先级
	USART3_Init(115200);//串口初始化为115200
	LCD_Init();
	POINT_COLOR=RED;
    
  while (1)
    {
		
		LCD_ShowString(0, 20, 240, 32, 24, (uint8_t *)"Hello world");
		Openmv_Receive_Data();
		Openmv_Data(); // 在主循环中处理 Openmv 数据
		
    }    		
	
 }

串口3初始化函数,这里我用的是串口3

/******************************************************************************
 * 函  数: vUSART3_Init
 * 功  能: 初始化USART的GPIO、通信参数配置、中断优先级
 *          (8位数据、无校验、1个停止位)
 * 参  数: uint32_t baudrate  通信波特率
 * 返回值: 无
 ******************************************************************************/
void USART3_Init(uint32_t baudrate)
{
    GPIO_InitTypeDef  GPIO_InitStructure;
    NVIC_InitTypeDef  NVIC_InitStructure;
    USART_InitTypeDef USART_InitStructure;

    // 时钟使能
    RCC->APB1ENR |= RCC_APB1ENR_USART3EN;                           // 使能USART3时钟
    RCC->APB2ENR |= RCC_APB2ENR_IOPBEN;                             // 使能GPIOB时钟

    // GPIO_TX引脚配置
    GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_10;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_AF_PP;                // TX引脚工作模式:复用推挽
    GPIO_Init(GPIOB, &GPIO_InitStructure);
    // GPIO_RX引脚配置
    GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_11;
    GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_IPU;                  // RX引脚工作模式:上拉输入; 如果使用浮空输入,引脚空置时可能产生误输入; 当电路上为一主多从电路时,可以使用复用开漏模式
    GPIO_Init(GPIOB, &GPIO_InitStructure);

    // NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
    // 中断配置
    NVIC_InitStructure .NVIC_IRQChannel = USART3_IRQn;
    NVIC_InitStructure .NVIC_IRQChannelPreemptionPriority = 2 ;     // 抢占优先级
    NVIC_InitStructure .NVIC_IRQChannelSubPriority = 2;             // 子优先级
    NVIC_InitStructure .NVIC_IRQChannelCmd = ENABLE;                // IRQ通道使能
    NVIC_Init(&NVIC_InitStructure);

    //USART 初始化设置
    USART_DeInit(USART3);
    USART_InitStructure.USART_BaudRate   = baudrate;                // 串口波特率
    USART_InitStructure.USART_WordLength = USART_WordLength_8b;     // 字长为8位数据格式
    USART_InitStructure.USART_StopBits   = USART_StopBits_1;        // 一个停止位
    USART_InitStructure.USART_Parity     = USART_Parity_No;         // 无奇偶校验位
    USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
    USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; // 使能收、发模式
    USART_Init(USART3, &USART_InitStructure);                       // 初始化串口

    USART_ITConfig(USART3, USART_IT_TXE, DISABLE);
    USART_ITConfig(USART3, USART_IT_RXNE, ENABLE);                  // 使能接受中断
    USART_ITConfig(USART3, USART_IT_IDLE, ENABLE);                  // 使能空闲中断

    USART_Cmd(USART3, ENABLE);  
    
    USART3->SR = ~(0x00F0);                                         // 清理中断

    xUSART.USART3InitFlag = 1;                                      // 标记初始化标志
    xUSART.USART3ReceivedNum = 0;                                   // 接收字节数清零

    printf("\rUSART3初始化配置      接收中断、空闲中断, 发送中断\r");
}

串口3中断函数

/******************************************************************************
 * 函  数: USART3_IRQHandler
 * 功  能: USART的接收中断、空闲中断、发送中断
 * 参  数: 无
 * 返回值: 无
 ******************************************************************************/
static uint8_t U3TxBuffer[256] ;    // 用于中断发送:环形缓冲区,256个字节
static uint8_t U3TxCounter = 0 ;    // 用于中断发送:标记已发送的字节数(环形)
static uint8_t U3TxCount   = 0 ;    // 用于中断发送:标记将要发送的字节数(环形)

void USART3_IRQHandler(void)
{
    static uint16_t cnt = 0;                                         // 接收字节数累计:每一帧数据已接收到的字节数
    static uint8_t  RxTemp[U3_RX_BUF_SIZE];                          // 接收数据缓存数组:每新接收1个字节,先顺序存放到这里,当一帧接收完(发生空闲中断), 再转存到全局变量:xUSART.USARTxReceivedBuffer[xx]中;
   
    // 接收中断
    if (USART3->SR & (1 << 5))                                       // 检查RXNE(读数据寄存器非空标志位); RXNE中断清理方法:读DR时自动清理;
    {
        if ((cnt >= U3_RX_BUF_SIZE))//||xUSART.USART3ReceivedFlag==1 // 判断1: 当前帧已接收到的数据量,已满(缓存区), 为避免溢出,本包后面接收到的数据直接舍弃.
        {
            // 判断2: 如果之前接收好的数据包还没处理,就放弃新数据,即,新数据帧不能覆盖旧数据帧,直至旧数据帧被处理.缺点:数据传输过快于处理速度时会掉包;好处:机制清晰,易于调试
            USART3->DR;                                              // 读取数据寄存器的数据,但不保存.主要作用:读DR时自动清理接收中断标志;
            return;
        }
        RxTemp[cnt++] = USART3->DR ;                                 // 把新收到的字节数据,顺序存放到RXTemp数组中;注意:读取DR时自动清零中断位;
    }

    // 空闲中断, 用于配合接收中断,以判断一帧数据的接收完成
    if (USART3->SR & (1 << 4))                                       // 检查IDLE(空闲中断标志位); IDLE中断标志清理方法:序列清零,USART1 ->SR;  USART1 ->DR;
    {
        xUSART.USART3ReceivedNum  = 0;                               // 把接收到的数据字节数清0
        memcpy(xUSART.USART3ReceivedBuffer, RxTemp, U3_RX_BUF_SIZE); // 把本帧接收到的数据,存放到全局变量xUSART.USARTxReceivedBuffer中, 等待处理; 注意:复制的是整个数组,包括0值,以方便字符串数据
        xUSART.USART3ReceivedNum  = cnt;                             // 把接收到的字节数,存放到全局变量xUSART.USARTxReceivedCNT中;
        cnt = 0;                                                     // 接收字节数累计器,清零; 准备下一次的接收
        memset(RxTemp, 0, U3_RX_BUF_SIZE);                           // 接收数据缓存数组,清零; 准备下一次的接收
        USART3 ->SR;
        USART3 ->DR;                                                 // 清零IDLE中断标志位!! 序列清零,顺序不能错!!
    }

    // 发送中断
    if ((USART3->SR & 1 << 7) && (USART3->CR1 & 1 << 7))             // 检查TXE(发送数据寄存器空)、TXEIE(发送缓冲区空中断使能)
    {
        USART3->DR = U3TxBuffer[U3TxCounter++];                      // 读取数据寄存器值;注意:读取DR时自动清零中断位;
        if (U3TxCounter == U3TxCount)
            USART3->CR1 &= ~(1 << 7);                                // 已发送完成,关闭发送缓冲区空置中断 TXEIE
    }
}

核心代码部分,接收OpenMV串口的数据和进行数据处理

#include "openmv.h"
#include "lcd.h"
#include "usart.h"
#include <stdint.h>

volatile uint8_t data_ready = 0;
int data = 0;
char str[100];
void Openmv_Receive_Data(void)//数据接收函数
{
    if (xUSART.USART3ReceivedNum >= 3)
    {
        if (xUSART.USART3ReceivedBuffer[0] == 0XA5 && xUSART.USART3ReceivedBuffer[2] == 0XA6)
        {
            data = xUSART.USART3ReceivedBuffer[1];
			sprintf(str,"X:%d",data);
			LCD_ShowNum(0, 80, data, 2, 24);
        }
        xUSART.USART3ReceivedNum = 0;
        data_ready = 1;
    }
}

void Openmv_Data(void) //数据处理函数
{
    // 定义显示的字符串
    const char *display_string = NULL;

    if (data_ready == 1) 
    {
        data_ready = 0; // 清除标志

        switch (data) 
        {
            case 1:
                display_string = "Phone ";
                break;
            case 2:
                display_string = "People";
			
                break;
            case 3:
                display_string = "Book  ";
                break;
            default:
                display_string = "Other ";
                break;
        }

        // 显示字符串到 LCD
        LCD_ShowString(0, 110, 240, 320, 24, (uint8_t *)display_string);
    }
}

三:运行结果

接线上我是将openmv的P4-TX接到stm32的B11,将P5-RX接到B10,如图,电源为usb接到电脑上供电。

识别结果,将openmv摄像头对向识别对象,按下openmv背部的key键进行拍摄,最后识别结果将显示到LCD屏上。

拍摄的key按键在openmv背部

5:结束语

本实验项目源文件已上传至夸克网盘上,需要友友的自取。
我用夸克网盘分享了「物体识别.zip」,点击链接即可保存。
链接:https://pan.quark.cn/s/02eef6a8cfa4
提取码:Tckb

参考来源:

超详细OpenMV与STM32单片机通信 (有完整版源码)-CSDN博客

OpenMV | 星瞳科技 (singtown.com)

STM32与OpenMv通信原理讲解_哔哩哔哩_bilibili

笔者第一次写博客,如有错误欢迎大家批评指正,如有疑问或者问题欢迎私信和讨论!

  • 16
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值