资料链接
https://book.openmv.cc/python-background.html
颜色阈值的设置
在OpenMV4中,使用到颜色识别,就避免不了需要对应颜色的阈值,通过阈值去查找到指定的颜色。
【这里以红色阈值设置为案例】
1、使用OpenMV IDE 连接摄像头
打开OpenMV IDE,连接上OpenMV摄像头。
2、开启OpenMV4采集颜色
案例是识别红色。我使用电脑上的画图软件,画出一个矩形。
然后使用摄像头采集图像数据到IDE。
3、打开阈值编辑器
在弹出的窗口中,选择帧缓冲区。
4、获取颜色阈值
通过调整滑块,将我们需要识别的颜色调整为白色,其他颜色调整为黑色。完成之后下方的LAB阈值就是颜色阈值了
案例一:串口通信
实现目标
使用OpenMV4采集最大色块的中心位置,通过串口自定义格式 发送给开发板。
这里选择使用PA8和PA9的附加串口1功能。
使用开发板上的串口1 连接 OpenMV4 的串口3(RX=P5 TX=P4)。
OpenMV4代码
内容讲解:查找图像中最大红色色块,通过串口3(P4 P5)输出数据格式 "[%d,%d]" 给开发板的串口。
# Untitled - By: Return - Tue Jun 11 2024
import sensor, image, time #导入传感器类、图像类、时间类
from pyb import UART #导入串口类
uart = UART(3, 9600) #实例一个串口3,波特率为9600 (TX=P4 RX=P5)
sensor.reset() # 复位和初始化摄像头传感器
sensor.set_pixformat(sensor.RGB565) # 设置像素格式为RGB565(或GRAYSCALE)
sensor.set_framesize(sensor.QVGA) # 设置帧大小为QVGA (320x240)
sensor.skip_frames(time = 2000) # 等待设置生效
clock = time.clock() # 创建一个时钟对象来跟踪图像帧率
#白色 颜色阈值
white_threshold = (49, 29, -128, 127, 37, 124)
# 查找最大色块函数
def find_max(blobs):
max_size=0
# 把blobs每个元素代入变量blob
for blob in blobs:
# 如果当前色块的像素量 跟之前比 是最多的
if blob.pixels() > max_size:
# 记录这个最多像素量的色块
max_blob=blob
# 更新最大像素量
max_size = blob.pixels()
return max_blob
uart.write("Hello World!\r") # 通过串口3输出固定字符串 “Hello World!”
# 主循环
while(True):
clock.tick() # 更新FPS时钟
img = sensor.snapshot() # 创建一个对象,拍一张照片并返回图像数据给img对象
print(clock.fps()) # IDE调试输出帧率
# 注意:当连接到IDE时,OpenMV Cam的运行速度约为一半。
# 一旦断开连接,FPS应该会增加。
# 在img对象中根据 传入的颜色阈值(white_threshold) 查找色块
blobs = img.find_blobs( [ white_threshold ] )
# 如果色块数量不为0
if blobs:
# 找到最大色块
max_blob=find_max(blobs)
# 在img图像中 对查找到的色块 画一个矩形
# blobs.rect() 色块的矩形参数
# draw_rectangle 画一个矩形框
img.draw_rectangle(max_blob.rect())
# 在图像上绘制一个十字。 cx代表中心x点 cy表示中心y点
img.draw_cross(max_blob.cx(), max_blob.cy())
# 整理字符串 将最大色块的 中心xy点 按照%d格式,格式化字符串
output_str="[%d,%d]" % ( max_blob.cx(), max_blob.cy() )
# 在IDE中输出字符串
print( 'Maximum color block position : ' + output_str + '\r\n' )
# 通过串口3输出字符串
uart.write( 'Maximum color block position : ' + output_str + '\r\n' )
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
开发板代码
接下来我们配置 SYSCONFIG
- 双击 empty.syscfg 文件,打开它。
- 在 empty.syscfg 文件界面点击 Tools,然后点击 SYSCONFIG 工具。
- 点击 ADD 添加配置
- 添加配置【根据下方图片进行添加】
- 点击保存
WARNING
出现只要出现下面的框就一定要选择:Yes to All
- 然后点击编译(可能会报错,我们不用管!)
- 然后我们所有设定的引脚和功能就会在 ti_msp_dl_config.h 中定义。因为这个文件我们包含进了 board.h 所以我们只需要引用 board.h 即可。【这里的 board.h 就充当了芯片头文件的作用】
移植步骤中的导入.c和.h文件与传感器章节的【DHT11温湿度传感器】相同,只是将.c和.h文件更改为bsp_openmv4.c与bsp_openmv4.h。这里不再过多讲述,移植完成后面修改相关代码。
在文件bsp_openmv4.c中,编写如下代码。
/*
* 立创开发板软硬件资料与相关扩展板软硬件资料官网全部开源
* 开发板官网:www.lckfb.com
* 技术支持常驻论坛,任何技术问题欢迎随时交流学习
* 立创论坛:https://oshwhub.com/forum
* 关注bilibili账号:【立创开发板】,掌握我们的最新动态!
* 不靠卖板赚钱,以培养中国工程师为己任
* Change Logs:
* Date Author Notes
* 2024-07-04 LCKFB first version
*/
#include "bsp_openmv4.h"
#include "stdio.h"
#include "string.h"
#define USART_RECEIVE_LENGTH 200
char openmv4_recv_buff[USART_RECEIVE_LENGTH]; // 接收缓冲区
volatile int openmv4_recv_length = 0; // 接收数据长度
volatile char openmv4_recv_complete_flag = 0; // 接收数据完成标志位
/******************************************************************
* 函 数 名 称:OpenMV4_usart_config
* 函 数 说 明:
* 函 数 形 参:
* 函 数 返 回:无
* 作 者:LC
* 备 注:LC
******************************************************************/
void OpenMV4_usart_config(void)
{
//清除串口中断标志
NVIC_ClearPendingIRQ(UART_1_INST_INT_IRQN);
//使能串口中断
NVIC_EnableIRQ(UART_1_INST_INT_IRQN);
}
/******************************************************************
* 函 数 名 称:Openmv4DataAnalysis
* 函 数 说 明:解析OpenMV4 发送过来的自定义格式数据 [%d,%d] 解析
* 函 数 形 参:无
* 函 数 返 回:无
* 作 者:LC
* 备 注:无
******************************************************************/
void Openmv4DataAnalysis(void)
{
char temp[20] = {0};
char *buff = NULL;
int head = 0;
int end = 0;
// 没有接收到数据 或者 数据没有接收完成 则不进行处理
if (openmv4_recv_complete_flag == 0) return;
// 关中断,防止在解析过程中接收到新数据
__disable_irq();
// 串口0输出接收到的数据(调试)
printf("%s\r\n", openmv4_recv_buff);
// 找到格式的头的第一个 '['
while ( (openmv4_recv_buff[head] != '[') && (head < openmv4_recv_length) )
{
head++;
}
if (head == openmv4_recv_length)
{
printf("NO Find Head!!\r\n");
// 清除接收完成标志位,等待下一次接收
openmv4_recv_complete_flag = 0;
openmv4_recv_length = 0;
// 开中断
__enable_irq();
return;
}
buff = &openmv4_recv_buff[head];
// 找到结尾
while ( (buff[end] != ']') && (end < openmv4_recv_length) )
{
end++;
}
if ((head + end) == openmv4_recv_length)
{
printf("NO Find end!!\r\n");
// 清除接收完成标志位,等待下一次接收
openmv4_recv_complete_flag = 0;
openmv4_recv_length = 0;
// 开中断
__enable_irq();
return;
}
// 从buff里复制长度为end的字符串到temp
if (end + 1 < sizeof(temp))
{
strncpy(temp, buff, end + 1);
// strncpy函数不会补零,需手动补上
temp[end + 1] = '\0';
printf("buff = %s\r\n", temp);
}
else
{
printf("Data too large for temp buffer!!\r\n");
}
// 清除接收完成标志位,等待下一次接收
openmv4_recv_complete_flag = 0;
openmv4_recv_length = 0;
// 清除数据
memset(openmv4_recv_buff, 0, USART_RECEIVE_LENGTH);
// 开中断
__enable_irq();
}
// 串口的中断服务函数
void UART_1_INST_IRQHandler(void)
{
uint8_t RecvDATA = 0;
// 如果产生了串口中断
switch (DL_UART_getPendingInterrupt(UART_1_INST))
{
case DL_UART_IIDX_RX: // 如果是接收中断
// 接收发送过来的数据保存
RecvDATA = DL_UART_Main_receiveData(UART_1_INST);
// 检查缓冲区是否已满
if (openmv4_recv_length < USART_RECEIVE_LENGTH - 1)
{
openmv4_recv_buff[openmv4_recv_length++] = RecvDATA;
openmv4_recv_buff[openmv4_recv_length] = '\0';
}
// 标记接收标志
openmv4_recv_complete_flag = 1;
break;
default: // 其他的串口中断
break;
}
}
void HardFault_Handler()
{
printf("entry Error!!!!\r\n");
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
在文件bsp_openmv4.h中,编写如下代码。
/*
* 立创开发板软硬件资料与相关扩展板软硬件资料官网全部开源
* 开发板官网:www.lckfb.com
* 技术支持常驻论坛,任何技术问题欢迎随时交流学习
* 立创论坛:https://oshwhub.com/forum
* 关注bilibili账号:【立创开发板】,掌握我们的最新动态!
* 不靠卖板赚钱,以培养中国工程师为己任
* Change Logs:
* Date Author Notes
* 2024-07-04 LCKFB first version
*/
#ifndef _BSP_OPENMV4_H_
#define _BSP_OPENMV4_H_
#include "board.h"
/* 串口缓冲区的数据长度 */
#define USART_RECEIVE_LENGTH 200
void OpenMV4_usart_config(void);
void Openmv4DataAnalysis(void);
#endif
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
案例验证
在empty.c中输入代码如下:
#include "board.h"
#include <stdio.h>
#include "bsp_openmv4.h"
int main(void)
{
//开发板初始化
board_init();
OpenMV4_usart_config(); // PENMV4串口初始化
printf("start\r\n");
while(1)
{
//解析OPENMV4发送过来的数据
Openmv4DataAnalysis();
delay_ms(500);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
上电效果:输出OPENMV4识别到的数据。
代码下载链接
案例二:任意颜色线循迹
接线
这里选择使用PA8和PA9的附加串口1功能。
使用开发板上的串口1 连接 OpenMV4 的串口3(RX=P5 TX=P4)。
OpenMV4代码
代码说明:实现任意颜色的寻线,需要知道线的颜色和位置。线的颜色识别通过色块查找的方式;而位置的判断,通过识别出线的颜色之后,在识别到的区域中心画一个十字型符号‘+’,并记录该色块中心位置命名为x_location。
如何识别线的颜色?通过寻找色块函数 find_blobs() 进行查找
将x_location与图像的X轴中心作比较,就可以得到线的位置Line_position。其中图像的X轴中心命名为centre
当 x_location > centre 时,Line_position设置为正数,x_location 越大,则Line_position 越大。
当 x_location < centre 时,Line_position设置为负数,x_location 越小,则Line_position 越小。
当 x_location = centre 时,Line_position设置为0。
如何实现循迹?
将OpenMV4放置在中间位置。
当Line_position 为负数,说明识别到线在图像的左侧。我们需要控制小车左转。
当Line_position 为正数,说明识别到线在图像的右侧。我们需要控制小车右转。
当Line_position 为0 , 说明识别到线在图像的中间。我们往前直行。
需要注意的是,线的颜色不可以和背景色相同,否则该案例失效。
import sensor, image, time, math # 调用声明
import display
from pyb import UART
import json
# 修改这里的颜色阈值可以做到任意颜色识别循迹
# 颜色阈值修改可以参考: ”颜色阈值选择工具“ https://book.openmv.cc/image/blob.html
GRAYSCALE_THRESHOLD = (0, 42, 22, -41, 27, -23)
# 识别颜色的矩形区域
Identification_region = [0, 50, 160, 20]
# 色块的X轴位置
x_location = 0
# 图像的X轴中心
centre = 0
# 线的位置
Line_position = 0
# 摄像头配置
sensor.reset() # 初始化摄像头传感器
sensor.set_pixformat(sensor.RGB565) # 设置相机的像素模式为16 bits/像素(即显示彩色)
sensor.set_framesize(sensor.QQVGA2) # 128x160 分辨率的相机传感器
sensor.skip_frames(30) # 跳过30帧,让相机图像在改变相机设置后稳定下来
lcd = display.SPIDisplay()
uart = UART(3, 9600) # 实例一个串口3,波特率为9600(TX=P4 RX=P5)
uart.init(9600, bits=8, parity=None, stop=1) # 配置串口的波特率、数据位、校验位、停止位
while (True):
img = sensor.snapshot() # 拍一张照片并返回图像
# 设置图像的X轴中心
centre = (img.width() / 2)
# 寻找 颜色阈值【GRAYSCALE_THRESHOLD】设置的颜色
blobs = img.find_blobs([GRAYSCALE_THRESHOLD], roi=Identification_region, merge=True)
# roi 是感兴趣区域的矩形元组(x,y,w,h)。如果指定[10,10,20,20],则在以(10,10)为起点的位置,以长宽都为20的大小查找色块
# 如果roi未指定,ROI即整个图像的图像矩形。 操作范围仅限于 roi 区域内的像素。
# merge 若为True,则合并所有没有被过滤掉的色块,这些色块的边界矩形互相交错重叠。
# 识别到色块
if blobs:
most_pixels = 0 # 最大色块的像素
largest_blob = 0 # 最大色块
for i in range(len(blobs)):
# 目标区域找到的颜色块(线段块)可能不止一个,找到最大的一个,作为本区域内的目标直线
if blobs[i].pixels() > most_pixels:
most_pixels = blobs[i].pixels() # 记录最大色块的像素
largest_blob = i # 记录最大色块
# 在查找到的最大色块处画矩形
img.draw_rectangle(blobs[largest_blob].rect())
# 在最大色块的中心画一个十字
img.draw_cross(blobs[largest_blob].cx(), blobs[largest_blob].cy())
# 记录中心点X轴位置
x_location = blobs[largest_blob].cx()
# 未识别到色块
else:
img.draw_string(2, 24, "!NONE!", color=(0, 0, 255), scale=2, mono_space=False)
x_location = centre
# 在图像的正中间画一个红色的十字,方便确定屏幕中间的位置
img.draw_cross(int(centre), int(img.height() / 2), color=(255, 0, 0), size=10)
# 在识别的区域内画蓝色矩形
img.draw_rectangle(Identification_region, color=(0, 0, 255))
# 计算色块距离屏幕中间的距离, (img.width() / 2)后,色块中心点在屏幕左边则是负值 色块中心点在屏幕右边则是正值
Line_position = "[%d]" % (x_location - centre)
# LCD显示图像,并在图像的(2,1)位置显示字符串output_str的内容,字符串颜色为蓝色,字体大小为2,字符间距不固定
img.draw_string(2, 1, "%s" % Line_position, color=(0, 0, 255), scale=2, mono_space=False)
lcd.write(img) # 显示图像。
# 串口3发送数据
uart.write(Line_position + '\r\n')
# IDE调试输出,以下内容可以删除
# 小于-10输出左转
# 大于 10输出右转
# 在-10到10范围内则输出前进
num = x_location - centre
if -10 <= num <= 10:
print("前进")
elif num > 10:
print("右转")
elif num < -10:
print("左转")
# 输出
print(Line_position)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
案例验证
案例三:矩形识别与中心判断
接线
这里选择使用PA8和PA9的附加串口1功能。
使用开发板上的串口1 连接 OpenMV4 的串口3(RX=P5 TX=P4)。
OpenMV4代码
import sensor, image, time, display
sensor.reset()
sensor.set_pixformat(sensor.RGB565)
sensor.set_framesize(sensor.QQVGA2)#像素128x160
sensor.skip_frames(time = 2000)
clock = time.clock()
display.SPIDisplay()
# 查找最大矩形块函数
def find_max(rects):
rect_max=0
# 把blobs每个元素代入变量rect
for rect in rects:
# 如果当前矩形的宽 跟之前比 是最大的
if rect.w() > rect_max:
global max_rect
# 记录这个最多大的矩形
max_rect = rect
# 更新最大矩形宽度
rect_max = rect.w()
#返回这个最大矩形元素
return max_rect
while(True):
#记录帧率
clock.tick()
#拍照
img = sensor.snapshot()
#查找到最大矩形块,查找范围x=0,y=20,w=128,h=120
rects = find_max(img.find_rects(roi=(0,20,128,120)))
#将查找到的最大矩形画一个绿色的矩形框
img.draw_rectangle(rects.x(),rects.y(),rects.w(),rects.h(),color=(0,255,0))
#计算矩形中心
#中心X定位:矩形像素宽度 / 2 + x轴起点位置
#中心Y定位:矩形像素高度 / 2 + y轴起点位置
x_central = (rects.w() / 2) + rects.x()
y_central = (rects.h() / 2) + rects.y()
#显示识别到的最大矩形中心的X,Y位置
img.draw_string(2,1,"x=%d y=%d"%(x_central,y_central))
#在查找到的矩形中心画一个蓝色的十字
img.draw_cross(int(x_central), int(y_central), color = (0, 0, 255), size = 5 )
#在查找区域画一个红色方框
#区域越小,帧数越高
img.draw_rectangle(0,20,128,120,color=(255,0,0))
#IDE输出帧率
print(clock.fps())
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
案例验证
案例四:任意角度矩形识别与边角定位
接线
这里选择使用PA8和PA9的附加串口1功能。
使用开发板上的串口1 连接 OpenMV4 的串口3(RX=P5 TX=P4)。
OpenMV4代码
import sensor, image, time
# 初始化摄像头
sensor.reset()
sensor.set_pixformat(sensor.RGB565) # 设置图像色彩格式为RGB565格式
sensor.set_framesize(sensor.QQVGA) # 设置图像大小为160*120
sensor.set_auto_whitebal(True) # 设置自动白平衡
sensor.set_brightness(3000) # 设置亮度为3000
sensor.skip_frames(time = 20) # 跳过帧
clock = time.clock()
while(True):
clock.tick()
img = sensor.snapshot()
#去除鱼眼畸变
img.lens_corr(1.8)
# -----矩形框部分-----
# 在图像中寻找矩形
for r in img.find_rects(threshold = 10000):
# 判断矩形边长是否符合要求
if r.w() > 20 and r.h() > 20:
# 在屏幕上框出矩形
img.draw_rectangle(r.rect(), color = (255, 0, 0), scale = 4)
# 获取矩形角点位置
corner = r.corners()
# 在屏幕上圈出矩形角点
img.draw_circle(corner[0][0], corner[0][1], 5, color = (255, 0, 0), thickness = 2, fill = False)
img.draw_circle(corner[1][0], corner[1][1], 5, color = (0, 255, 0), thickness = 2, fill = False)
img.draw_circle(corner[2][0], corner[2][1], 5, color = (0, 0, 255), thickness = 2, fill = False)
img.draw_circle(corner[3][0], corner[3][1], 5, color = (255, 255, 0), thickness = 2, fill = False)
# 打印四个角点坐标, 角点1的数组是corner[0], 坐标就是(corner[0][0],corner[0][1])
# 角点检测输出的角点排序每次不一定一致,矩形左上的角点有可能是corner0,1,2,3其中一个
left_down_dot ="LD=[%d,%d]"%(corner[0][0],corner[0][1]) #红色点
right_down_dot ="RD=[%d,%d]"%(corner[1][0],corner[1][1]) #绿色点
right_up_dot ="RU=[%d,%d]"%(corner[2][0],corner[2][1]) #蓝色点
left_up_dot ="LU=[%d,%d]"%(corner[3][0],corner[3][1]) #黄色点
print(left_up_dot + "\n" + left_down_dot + "\n" + right_up_dot + "\n" + right_down_dot)
# 打印帧率
print(clock.fps())
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
案例验证
案例五:激光识别与定位
将识别到的绿色激光以黑色方框圈出,并在IDE中输出激光XY轴位置。
接线
这里选择使用PA8和PA9的附加串口1功能。
使用开发板上的串口1 连接 OpenMV4 的串口3(RX=P5 TX=P4)。
OpenMV4代码
import sensor, image, time
# 初始化摄像头
sensor.reset()
sensor.set_pixformat(sensor.RGB565) # 设置图像色彩格式为RGB565格式
sensor.set_framesize(sensor.QQVGA) # 设置图像大小为160*120
sensor.set_auto_whitebal(True) # 设置自动白平衡
sensor.set_brightness(3000) # 设置亮度为3000
sensor.skip_frames(time = 20) # 跳过帧
clock = time.clock()
while(True):
clock.tick()
img = sensor.snapshot()
#去除鱼眼畸变
img.lens_corr(1.8)
# -----跟踪激光部分-----
# 设置绿色激光颜色阈值
G_td = [(98, 81, -48, 41, 14, 72)]
# 根据阈值找到色块
for b in img.find_blobs(G_td,pixels_threshold=2, area_threshold=15, merge=True,invert = 0):
# 在识别到的激光处画一个黑色矩形
img.draw_rectangle(b.rect(), color = (0, 0, 0), scale = 1, thickness = 2)
# 打印激光色块的中心位置
# 使用b.x()获取色块矩形左上角X坐标
# 使用b.y()获取色块矩形左上角Y坐标
# 使用b.w()获取色块矩形宽度
# 使用b.h()获取色块矩形高度
# 矩形中心坐标为(x + w/2,y + h/2)
print("[%d,%d]"%( (b.x() + b.w()/2), (b.y() + b.h()/2 ) ))
break
# 打印帧率
print(clock.fps())
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
案例验证
注意
如识别不出绿色,请按照颜色阈值的设置章节,配置你的绿色阈值。