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博客
STM32与OpenMv通信原理讲解_哔哩哔哩_bilibili
笔者第一次写博客,如有错误欢迎大家批评指正,如有疑问或者问题欢迎私信和讨论!