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) 當然,別忘了共地哈。