OPenMV識別顏色識別物塊及檢測二維碼的進階應用

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章