OPenMV摄像头入门与识别颜色物块及二维码的进阶应用
OpenMV简介
简单的来说,它是一个可编程的摄像头,通过MicroPython语言,可以实现你的逻辑。
而且摄像头本身内置了一些图像处理算法,很容易使用。
适合做DIY相关的项目制作,比如追踪小球的车,云台,或者解魔方的机器人。以及对成本要求很高的嵌入式工业方案,比如流水线物品的分拣。
足以满足基本的应用功能。目前最新版为OpenMV4,本文也是采用最新版而成。
前言
关于openmv IDE的安装与使用,官网就有教程,在此不再赘述。
笔者以前并未学过Python,但只要有C/C++的基础,对OpenMV编程就不是难事。因为IDE自带了很多例程,在这基础上修改即可。
软件介绍
我这次是做嵌入式开发,针对各种颜色的物块进行识别,并且完成了二维码识别。使用stm32F4系列的单片机与OpenMV4进行串口通信。完成这些功能就可以对OpenMV驾轻就熟了。
识别二维码
二维码是当今重要的信息载体,并且发展迅速,应用前景很广。
下附源码:
sensor.reset()
sensor.set_pixformat(sensor.RGB565)
sensor.set_framesize(sensor.QVGA) # can be QVGA on M7...
sensor.skip_frames(30) # 修改sensor配置之后, 跳过30帧
sensor.set_auto_gain(False) # must turn this off to prevent image washout...
while(a):
img = sensor.snapshot()
img.lens_corr(1.8) # strength of 1.8 is good for the 2.8mm lens.
for code in img.find_qrcodes():
值得注意的是,由于物体的距离不同,我们可以采用不同的像素去采集图像,所以在第三行中,sensor.set_framesize(sensor.QVGA)我们可以根据需要,选择QQVGA、QVGA、VGA等模式。
倒数第二行img.lens_corr(1.8) # strength of 1.8 is good for the 2.8mm lens.
这是对图像进行校正,参数可以自行修改。
至此二维码已经成功读取到ode.payload()中, 例如下图调用。
if code.payload() == “123”:
qr=1;
识别颜色及物块位置
我认为这是在嵌入式设计和日常应用中最为常用的功能。
从颜色格式而言,这里是LAB颜色模型,而不是常见的RGB。
这里有三对阈值参数
1.亮度,范围[0,100],从纯黑到纯白;2. a表示从红色到绿色的范围,[127,-128];3. b表示从黄色到蓝色范围,是[127,-128]
white_threshold_01 = ((95, 100, -18, 3, -8, 4)); #白色阈值
red_threshold_01 = ((35, 100, 41, 77, 24, 59));
green_threshold_01 = ((50, 100, -80, -20, 8, 20));
blue_threshold_01 = ((20, 100, -18, 18, -80, -30));
while(True):
clock.tick() # Track elapsed milliseconds between snapshots().
img = sensor.snapshot() # Take a picture and return the image.
# pixels_threshold=100, area_threshold=100
blobs = img.find_blobs([red_threshold_01], pixels_threshold=100, area_threshold=100, merge=True, margin=10); #红色物块
blobs1 = img.find_blobs([green_threshold_01], pixels_threshold=100, area_threshold=100, merge=True, margin=10); #绿色物块
blobs2 = img.find_blobs([blue_threshold_01], pixels_threshold=100, area_threshold=100, merge=True, margin=10); #蓝色物块
cx=0;cy=0;cx1=0;cy1=0;cx2=0;cy2=0;
if blobs:
#如果找到了目标红色
max_b = find_max(blobs);
# Draw a rect around the blob.
img.draw_rectangle(max_b[0:4]) # rect
#用矩形标记出目标颜色区域
img.draw_cross(max_b[5], max_b[6]) # cx, cy
img.draw_cross(160, 120) # 在中心点画标记
#在目标颜色区域的中心画十字形标记
cx=max_b[5];
cy=max_b[6];
img.draw_line((160,120,cx,cy), color=(127));
#img.draw_string(160,120, "(%d, %d)"%(160,120), color=(127));
img.draw_string(cx, cy, "(%d, %d)"%(cx,cy), color=(127));
blob = blobs[0]
img_ball_r= calc_radius(blob)
# 小球离镜头的距离 根据我们的公式计算
ball_distance = K / img_ball_r
print("小球距离: %d"%ball_distance)
串口设置与stm32通信
OpenMV自带几种通讯方式:串口、I2C、SPI
笔者采用最为方便的串口通讯。
注意这里的波特率,要配置一致。
uart = UART(3,115200) #定义串口3变量
uart.init(115200, bits=8, parity=None, stop=1) # init with given parameters
然后定义串口发送数据包的格式:
def sending_data(code,cx,cy,cx1,cy1,cx2,cy2):
global uart;
#frame=[0x2C,18,cx%0xff,int(cx/0xff),cy%0xff,int(cy/0xff),0x5B];
#data = bytearray(frame)
data = ustruct.pack("<bbhhhhhhhb", #格式为俩个字符俩个短整型(2字节)
0x2C, #帧头1
0x12, #帧头2
int(code), # up sample by 4 #数据1
int(cx), # up sample by 4 #数据1
int(cy), # up sample by 4 #数据2
int(cx1), # up sample by 4 #数据1
int(cy1), # up sample by 4 #数据2
int(cx2), # up sample by 4 #数据1
int(cy2), # up sample by 4 #数据2
0x5B)
uart.write(data); #必须要传入一个字节数组
我设置了两个帧头,一个帧尾,均为一字节,如下对应“b”。中间的数据为7个两字节,如下对应“h”。
data = ustruct.pack("<bbhhhhhhhb",
这一行中可以对应着修改,“b”、"h"等以下表对应。
#pack各字母对应类型
#x pad byte no value 1
#c char string of length 1 1
#b signed char integer 1
#B unsigned char integer 1
#? _Bool bool 1
#h short integer 2
#H unsigned short integer 2
#i int integer 4
#I unsigned int integer or long 4
#l long integer 4
#L unsigned long long 4
#q long long long 8
#Q unsilong long long 8
#f float float 4
#d double float 8
#s char[] string 1
#p char[] string 1
#P void * long
串口接收函数:
def recive_data():
global uart
if uart.any():
tmp_data = uart.readline();
print(tmp_data)
注:本人只让OpenMV对单片机单向通信,所以在例程中并未使用此串口接收函数。
stm32例程:
OpenMV.h
#ifndef __Openmv_H
#define __Openmv_H
#include "usart.h"
struct openmv
{
s16 qr;
s16 a;
s16 b;
s16 c;
s16 d;
s16 e;
s16 f;
};
void OpenMV_Handler(UART_HandleTypeDef *huart);
#endif
OpenMV.c
#include "OpenMV.h"
#include <string.h>
struct openmv stcopenmv;
extern u8 RxTemp ;
void OpenMV_Handler(UART_HandleTypeDef *huart)
{
/* Prevent unused argument(s) compilation warning */
static u8 aRxBuffer[20];
static u8 aRxCnt = 0;
static u8 state = 0;
if (state == 0&&RxTemp==0x2C) //数据头不对,则重新开始寻找0x2c数据头
{
state = 1;
}
else if (state == 1&&RxTemp==0x12) //数据头不对,则重新开始寻找0x12数据头
{
state =2;
aRxCnt=0;
}
else if (state == 2) //数据不满11个,则返回
{
aRxBuffer[aRxCnt++]=RxTemp;
if (aRxCnt>=14)//数据不满11个,返回
{
memcpy(&stcopenmv,&aRxBuffer[0],14);
aRxCnt=0;
state=0;
}
}
else state=0;
/* NOTE: This function should not be modified, when the callback is needed,
the HAL_UART_RxCpltCallback could be implemented in the user file
*/
}
int COLOR_Order()
{
if(stcopenmv.a<stcopenmv.c&&stcopenmv.a<stcopenmv.e)
{
if(stcopenmv.c<stcopenmv.e)
return 1;
else
return 2;
}
else if(stcopenmv.c<stcopenmv.a&&stcopenmv.c<stcopenmv.e)
{
if(stcopenmv.a<stcopenmv.e)
return 3;
else
return 4;
}
else if(stcopenmv.e<stcopenmv.c&&stcopenmv.e<stcopenmv.a)
{
if(stcopenmv.a<stcopenmv.c)
return 5;
else
return 6;
}
return 0;
}
main函数调用的相关代码:
#include "OpenMV.h"
extern u8 RxTemp ;
extern struct openmv stcopenmv;
MX_USART3_UART_Init();
HAL_UART_Receive_IT(&huart3,&RxTemp,1);
经过以上初始化,就已经可以读取OpenMV串口发过来的值了。
比如以下就可以通过我自己的LED函数显示出来:
其中的stcopenmv.qr是读取二维码的值。
OLED_ShowNum(0,0,stcopenmv.a,3,16);
OLED_ShowNum(100,0,stcopenmv.qr,3,16);
OLED_ShowNum(0,2,stcopenmv.c,3,16);
OLED_ShowNum(0,4,stcopenmv.e,3,16);
OLED_ShowNum(100,4,stcopenmv.f,3,16);
OpenMV编辑器全例程如下:
import sensor, image, time, math
from pyb import UART
import json
import ustruct
#亮度,范围[0,100],从纯黑到纯白;a表示从红色到绿色的范围,[127,-128];b表示从黄色到蓝色范围,是[127,-128]
white_threshold_01 = ((95, 100, -18, 3, -8, 4)); #白色阈值
red_threshold_01 = ((35, 100, 41, 77, 24, 59));
green_threshold_01 = ((50, 100, -80, -20, 8, 20));
blue_threshold_01 = ((20, 100, -18, 18, -80, -30));
threshold = [50, 50, 0, 0, 0, 0] # Middle L, A, B values.
QRCode_1="123";
QRCode_2="132";
QRCode_3="213";
QRCode_4="231";
QRCode_5="312";
QRCode_6="321";
qr=0
a=1
K=5000 # K是我们计算出来的常数
sensor.reset()
sensor.set_pixformat(sensor.RGB565)
sensor.set_framesize(sensor.QVGA) # can be QVGA on M7...
sensor.skip_frames(30) # 修改sensor配置之后, 跳过30帧
sensor.set_auto_gain(False) # must turn this off to prevent image washout...
while(a):
img = sensor.snapshot()
img.lens_corr(1.8) # strength of 1.8 is good for the 2.8mm lens.
for code in img.find_qrcodes():
if code.payload() == QRCode_1 :
qr=1;
elif code.payload() == QRCode_2 :
qr=2;
elif code.payload() == QRCode_3 :
qr=3;
elif code.payload() == QRCode_4 :
qr=4;0
.3
elif code.payload() == QRCode_5 :
qr=5;
elif code.payload() == QRCode_6 :
qr=6;
else :
qr=7;
a=0
print(code)
print(qr)
print(QRCode_4)
else:
sensor.reset()
sensor.set_pixformat(sensor.RGB565)
sensor.set_framesize(sensor.QVGA)
sensor.skip_frames(time = 2000)
sensor.set_auto_gain(False) # must be turned off for color tracking
sensor.set_auto_whitebal(False) # must be turned off for color tracking
clock = time.clock()
uart = UART(3,115200) #定义串口3变量
uart.init(115200, bits=8, parity=None, stop=1) # init with given parameters
def calc_radius(blob):
# 计算图像中色块的半径 , 比较粗暴
return (blob.w() + blob.h()) / 2
def find_max(blobs): #定义寻找色块面积最大的函数
max_size=0
for blob in blobs:
if blob.pixels() > max_size:
max_blob=blob
max_size = blob.pixels()
return max_blob
def sending_data(code,cx,cy,cx1,cy1,cx2,cy2):
global uart;
#frame=[0x2C,18,cx%0xff,int(cx/0xff),cy%0xff,int(cy/0xff),0x5B];
#data = bytearray(frame)
data = ustruct.pack("<bbhhhhhhhb", #格式为俩个字符俩个短整型(2字节)
0x2C, #帧头1
0x12, #帧头2
int(code), # up sample by 4 #数据1
int(cx), # up sample by 4 #数据1
int(cy), # up sample by 4 #数据2
int(cx1), # up sample by 4 #数据1
int(cy1), # up sample by 4 #数据2
int(cx2), # up sample by 4 #数据1
int(cy2), # up sample by 4 #数据2
0x5B)
uart.write(data); #必须要传入一个字节数组
def recive_data():
global uart
if uart.any():
tmp_data = uart.readline();
print(tmp_data)
#mainloop
while(True):
clock.tick() # Track elapsed milliseconds between snapshots().
img = sensor.snapshot() # Take a picture and return the image.
# pixels_threshold=100, area_threshold=100
blobs = img.find_blobs([red_threshold_01], pixels_threshold=100, area_threshold=100, merge=True, margin=10);
blobs1 = img.find_blobs([green_threshold_01], pixels_threshold=100, area_threshold=100, merge=True, margin=10);
blobs2 = img.find_blobs([blue_threshold_01], pixels_threshold=100, area_threshold=100, merge=True, margin=10);
cx=0;cy=0;cx1=0;cy1=0;cx2=0;cy2=0;
if blobs:
#如果找到了目标颜色
max_b = find_max(blobs);
# Draw a rect around the blob.
img.draw_rectangle(max_b[0:4]) # rect
#用矩形标记出目标颜色区域
img.draw_cross(max_b[5], max_b[6]) # cx, cy
img.draw_cross(160, 120) # 在中心点画标记
#在目标颜色区域的中心画十字形标记
cx=max_b[5];
cy=max_b[6];
img.draw_line((160,120,cx,cy), color=(127));
#img.draw_string(160,120, "(%d, %d)"%(160,120), color=(127));
img.draw_string(cx, cy, "(%d, %d)"%(cx,cy), color=(127));
blob = blobs[0]
img_ball_r= calc_radius(blob)
# 小球离镜头的距离 根据我们的公式计算
ball_distance = K / img_ball_r
print("小球距离: %d"%ball_distance)
if blobs1:
#如果找到了目标颜色
max_b = find_max(blobs1);
# Draw a rect around the blob.
img.draw_rectangle(max_b[0:4]) # rect
#用矩形标记出目标颜色区域
img.draw_cross(max_b[5], max_b[6]) # cx, cy
img.draw_cross(160, 120) # 在中心点画标记
#在目标颜色区域的中心画十字形标记
cx1=max_b[5];
cy1=max_b[6];
img.draw_line((160,120,cx1,cy1), color=(127));
#img.draw_string(160,120, "(%d, %d)"%(160,120), color=(127));
img.draw_string(cx1, cy1, "(%d, %d)"%(cx1,cy1), color=(127));
if blobs2:
#如果找到了目标颜色
max_b = find_max(blobs2);
# Draw a rect around the blob.
img.draw_rectangle(max_b[0:4]) # rect
#用矩形标记出目标颜色区域
img.draw_cross(max_b[5], max_b[6]) # cx, cy
img.draw_cross(160, 120) # 在中心点画标记
#在目标颜色区域的中心画十字形标记
cx2=max_b[5];
cy2=max_b[6];
img.draw_line((160,120,cx2,cy2), color=(127));
#img.draw_string(160,120, "(%d, %d)"%(160,120), color=(127));
img.draw_string(cx2, cy2, "(%d, %d)"%(cx2,cy2), color=(127));
# sending_data(cx,cy,cx,cy,cx,cy); #发送点位坐标
sending_data(qr,cx,cy,cx1,cy1,cx2,cy2); #发送点位坐标
recive_data();
#time.sleep(1000)
#pack各字母对应类型
#x pad byte no value 1
#c char string of length 1 1
#b signed char integer 1
#B unsigned char integer 1
#? _Bool bool 1
#h short integer 2
#H unsigned short integer 2
#i int integer 4
#I unsigned int integer or long 4
#l long integer 4
#L unsigned long long 4
#q long long long 8
#Q unsilong long long 8
#f float float 4
#d double float 8
#s char[] string 1
#p char[] string 1
#P void * long
硬件
电源
通过USB-type c线供电。据笔者实验,这是仅有的供电方式,如果这里坏了,就得重新买一个了。
焊接接口
笔者用的是串口,就连usart3(P10、11) 当然,别忘了共地哈。