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

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