本人大四学生,电赛生涯已经走到尽头,一路上踩过不少坑,但运气也不错拿了两年省一,思来想去,决定开始写博客,将电赛经验分享一二,能力有限,高手轻喷。
本篇主要是介绍 K210 上的 image 机器视觉模块。如果你要使用 K210 来进行一些颜色追踪(比如识别黑色,传回坐标让小车寻线,或者是识别其他颜色的线),那么你首先要做的就是将 sensor 配置好,如果配置不好,会直接影响颜色追踪的效果。
sensro的配置可以看我上一篇学习笔记,传送门
【K210】K210学习笔记一——sensor
如果你配置完了,就可以接着学习如何用 image 模块做一些事情啦!
本文着重于 image 模块中的一个函数 find_blobs 也就是寻找色块的函数,因为多次比赛使用下来,给我的感觉就是 image 模块中最好用的便是寻找色块这个函数。其他的函数做的都比较差(个人感觉),比如识别形状的那几个函数,但其实找色块也是可以识别形状的。我这两年来做无人机题的识别都靠的是这一个函数,另外这两年的小车题靠这个函数也是可以的,只要你懂得怎么处理,我就分享一下我的使用心得,另外这个作为 image 模块的使用贴,后续如果我发现了其他函数有什么好用之处,也会在这里更新。
这里定义了颜色属性类,主要是用于保存找到的色块的一些信息,以及色块的阈值,寻找区域等信息。
#__________________________________________________________________
# 寻找色块
# 定义类
class color_property():
cx = 0 # 色块 x轴 中心坐标
cy = 0 # 色块 y轴 中心坐标
flag = 0 # 色块标志位 1 找到 0 未找到
color = 0 # 色块颜色标志位 例如 你可以用 1 来表示 黑色
density = 0 # 色块密度比 反映色块锁定程度 值越大 锁定程度越好
pixels_max = 0 # 色块像素最大值
led_flag = 0 # LED标志位 方便调试用
color_threshold = (0, 0, 0, 0, 0, 0) # 色块颜色阈值
color_roi = (0,0,320,240) # 色块寻找区域(感兴趣区域)
color_x_stride = 1 # 色块 x轴 像素最小宽度 色块如果比较大可以调大此参数 提高寻找速度
color_y_stride = 1 # 色块 y轴 像素最小宽度 色块如果比较大可以调大此参数 提高寻找速度
color_pixels_threshold = 100 # 色块 像素个数阈值 例如调节此参数为100 则可以滤除色块像素小于100的色块
color_area_threshold = 100 # 色块 被框面积阈值 例如调节此参数为100 则可以滤除色块被框面积小于100的色块
color_merge = True # 是否合并寻找到的色块 True 则合并 False 则不合并
color_margin = 1 # 色块合并间距 例如调节此参数为1 若上面选择True合并色块 且被找到的色块有多个 相距1像素 则会将这些色块合并
这里我实例化了4个类,分别是黑色、红色、绿色、蓝色。举例我只举例两个,一个是找黑色色块(我在无人机上用这个方法找到黑色降落点),一个是找红色线(去年小车题就可以找红线),绿色和蓝色预留。当然你也可以依据你的需要定义找其他颜色。
# 实例化类
# 黑色
black = color_property()
black.color_threshold = (0, 50, -10, 10, -10, 10)
black.color_roi = (0,0,320,240)
black.color_x_stride = 1
black.color_y_stride = 1
black.color_pixels_threshold = 100
black.color_area_threshold = 100
black.color_merge = True
black.color_margin = 1
# 红色
red = color_property()
red.color_threshold = (0, 100, 20, 127, -5, 127)
#red.color_roi = (0,0,320,240)
red.color_roi = (0,110,320,20)
red.color_x_stride = 1
red.color_y_stride = 1
#red.color_pixels_threshold = 100
#red.color_area_threshold = 100
red.color_pixels_threshold = 10
red.color_area_threshold = 10
red.color_merge = True
red.color_margin = 1
# 绿色 预留
green = color_property()
# 蓝色 预留
blue = color_property()
第一个参数 black.color_threshold 是设置黑色的阈值,可由阈值编辑器得到(直接在阈值编辑器中复制,然后粘贴到 black.color_threshold = 后面,覆盖掉原来的阈值即可)。阈值这个参数不是万能的,要依据个人实际情况进行设置,最准确的就是上电运行,将摄像头对着被追踪物体,然后停止运行,打开阈值编辑器取阈值。
第二个参数是设置色块寻找区域,对于 QVGA ,全屏寻找就设置成(0,0,320,240),这四个参数分别代表的是x,y,w,h,即x轴开始坐标,y轴开始坐标,x轴宽度,y轴高度。x轴和y轴的零点坐标都在图像的左上方。这个值略微修改就可以摇身一变,让寻找色块变成巡线。比如你可以设置这个值为(0,110,320,20),就表示你要从x轴的0坐标开始,到x轴的320,y轴的110坐标开始,到y轴的130,寻找色块,这样一来,就可以知道小车的当前位置是在x轴哪里,x轴中心是160,如果当前位置小于160,就左转,大于160,就右转,这样就可以完成巡线。
第三和第四个参数是设置色块x轴、y轴上像素的最小宽度,如果被寻找的目标比较大,可以调大这两个参数提高寻找速度。
第五个参数 black.color_pixels_threshold 是设置被找色块像素的个数阈值,如果被找到的色块像素个数少于这个值,将会被滤除,这是作为一种辅助手段,排除环境干扰的好方法。
第六个参数 black.color_area_threshold 是设置被找色块被框面积的个数阈值,如果被找到的色块被框面积少于这个值,将会被滤除,这是作为一种辅助手段,排除环境干扰的好方法。
第七个参数 black.color_merge 是需不需要合并找到的像素,一般都是需要的。
第八个参数 black.color_margin 是控制色块合并间距,例如调节此参数为1,若上面选择True合并色块且被找到的色块有多个,它们之间如果相距1像素,则会将这些色块合并。
# 实例化类
# 黑色
black = color_property()
black.color_threshold = (0, 50, -10, 10, -10, 10)
black.color_roi = (0,0,320,240)
black.color_x_stride = 1
black.color_y_stride = 1
black.color_pixels_threshold = 100
black.color_area_threshold = 100
black.color_merge = True
black.color_margin = 1
阈值编辑器打开方式,源图像位置来自帧缓冲区。
阈值编辑器的使用方法很简单,拖动进度条改变 LAB 阈值即可,白色是被追踪的颜色阈值,保证白色尽量集中在被追踪的物体上即可。
各参数的设置原因已经在黑色设置中说明了,通过对比我们可以发现,相对于黑色,红色这里的设置是将阈值改成了红色阈值,压缩了y轴的感兴趣区域,调小了滤除色块的参数,如果用于巡红线,就可以获取红线的位置信息。
# 红色
red = color_property()
red.color_threshold = (0, 100, 20, 127, -5, 127)
#red.color_roi = (0,0,320,240)
red.color_roi = (0,110,320,20)
red.color_x_stride = 1
red.color_y_stride = 1
#red.color_pixels_threshold = 100
#red.color_area_threshold = 100
red.color_pixels_threshold = 10
red.color_area_threshold = 10
red.color_merge = True
red.color_margin = 1
这里定义了寻找色块的函数,使用的时候将实例类传入即可。led_flag是为了方便脱机运行的时候判断识别效果,后续文章会介绍到怎么用,这里先定义。
# 定义寻找色块函数
def opv_find_blobs(color,led_flag):
color.pixels_max = 0 # 重置 色块 最大像素数量
color.flag = 0 # 重置 色块 标志位
color.led_flag = 0 # 重置 led 标志位
for blobs in img.find_blobs([color.color_threshold], # 色块颜色阈值
roi = color.color_roi, # 色块寻找区域(感兴趣区域)
x_stride = color.color_x_stride, # 色块 x轴 像素最小宽度 色块如果比较大可以调大此参数 提高寻找速度
y_stride = color.color_y_stride, # 色块 y轴 像素最小宽度 色块如果比较大可以调大此参数 提高寻找速度
pixels_threshold = color.color_pixels_threshold, # 色块 像素个数阈值 例如调节此参数为100 则可以滤除色块像素小于100的色块
area_threshold = color.color_area_threshold, # 色块 被框面积阈值 例如调节此参数为100 则可以滤除色块被框面积小于100的色块
merge = color.color_merge, # 是否合并寻找到的色块 True 则合并 False 则不合并
margin = color.color_margin): # 色块合并间距 例如调节此参数为1 若上面选择True合并色块 且被找到的色块有多个 相距1像素 则会将这些色块合并
img.draw_rectangle(blobs[0:4]) # 圈出找到的色块
if color.pixels_max < blobs.pixels(): # 找到面积最大的色块
color.pixels_max = blobs.pixels()
color.cx = blobs.cx() # 将面积最大的色块的 x轴 中心坐标值 赋值给 color
color.cy = blobs.cy() # 将面积最大的色块的 y轴 中心坐标值 赋值给 color
color.flag = 1 # 标志画面中有找到色块
color.density = blobs.density() # 将面积最大的色块的 色块密度比 赋值给 color
color.led_flag = led_flag # 将控制led颜色的标志位的值 赋值给 color
if color.flag == 1: # 标记画面中被找到的最大色块的中心坐标
img.draw_cross(color.cx,color.cy, color=127, size = 15)
img.draw_circle(color.cx,color.cy, 15, color = 127)
测试寻找色块之前,需要定义一个函数,用来打印被寻找到的色块信息,方便调试使用。
# 定义打印色块参数函数
def print_blobs_property(color,name):
print(name,"cx:",color.cx,"cy:",color.cy,"flag:",color.flag,"color:",color.color,"density:",color.density,"led_flag:",color.led_flag)
主函数如下所示,这里寻找两个色块,一个是寻找黑色色块,另一个是寻找红线,然后间隔一定时间,将结果打印出来。
#__________________________________________________________________
# 主函数
while(True):
clock.tick() # 跟踪运行时间
img = sensor.snapshot() # 拍摄一张照片
opv_find_blobs(black,1) # 找黑色色块 led标志为1 表示黑色
opv_find_blobs(red,2) # 找红色色块 led标志为2 表示红色
if mycnt == 0: # 如果 mycnt 等于 0 此步骤的目的是控制打印周期 不要打印的太快
mycnt = 1 # 将 1 赋值给 mycnt 使下一次不再满足 mycnt == 0 进入 elif
print_sensor() # 打印sensor参数
print_blobs_property(black,"Black-") # 打印黑色色块参数
print_blobs_property(red, "Red- ") # 打印红色色块参数
elif mycnt < mycnt_max: # 计数变量 小于 计数上限 则 计数变量 自增
mycnt = mycnt + 1
else: # 计数变量 超出 计数上限 则 将0赋值给 mycnt 使下一次进入 if
mycnt = 0
黑色色块测试结果,可以看到寻找效果还不错。
红线测试效果,可以看到寻找效果还不错。
完整源码如下所示,大家可以复制该源码,进行测试,下一次学习笔记将会记录K210按键的配置、LED的配置、LCD显示屏的配置,我们下期再见~!
# image_V1.0 - By: FITQY - 周一 8 月 22 日 2022
#__________________________________________________________________
# 导入模块
import sensor, time, image # 导入感光元件模块 sensor 跟踪运行时间模块 time 机器视觉模块 image
#__________________________________________________________________
# 感光元件设置
sensor.reset() # 重置并初始化感光元件 默认设置为 摄像头频率 24M 不开启双缓冲模式
#sensor.reset(freq=24000000, dual_buff=True) # 设置摄像头频率 24M 开启双缓冲模式 会提高帧率 但内存占用增加
sensor.set_pixformat(sensor.RGB565) # 设置图像格式为 RGB565 (彩色) 除此之外 还可设置格式为 GRAYSCALE 或者 YUV422
sensor.set_framesize(sensor.QVGA) # 设置图像大小为 QVGA (320 x 240) 像素个数 76800 K210最大支持格式为 VGA
sensor.set_auto_exposure(1) # 设置自动曝光
#sensor.set_auto_exposure(0, exposure=120000) # 设置手动曝光 曝光时间 120000 us
sensor.set_auto_gain(0, gain_db = 12) # 设置画面增益 17 dB 影响实时画面亮度
sensor.set_auto_whitebal(0, rgb_gain_db = (0,0,0)) # 设置RGB增益 0 0 0 dB 影响画面色彩呈现效果 在 K210 上无法调节增益 初步判定是感光元件 ov2640 无法支持
#sensor.set_contrast(0) # 设置对比度 0 这个参数无法读取 且调这个参数对画面似乎不会产生影响 暂时注释
#sensor.set_brightness(0) # 设置亮度 0 这个参数无法读取 且调这个参数对画面似乎不会产生影响 暂时注释
#sensor.set_saturation(0) # 设置饱和度 0 这个参数无法读取 且调这个参数对画面似乎不会产生影响 暂时注释
sensor.set_vflip(1) # 打开垂直翻转 如果是 01Studio 的 K210 不开启会导致画面方向与运动方向相反
sensor.set_hmirror(1) # 打开水平镜像 如果是 01Studio 的 K210 不开启会导致画面方向与运动方向相反
sensor.skip_frames(time = 2000) # 延时跳过2s 等待感光元件稳定
#__________________________________________________________________
# 创建时钟对象
clock = time.clock() # 创建时钟对象 clock
#__________________________________________________________________
# 寻找色块
# 定义类
class color_property():
cx = 0 # 色块 x轴 中心坐标
cy = 0 # 色块 y轴 中心坐标
flag = 0 # 色块标志位 1 找到 0 未找到
color = 0 # 色块颜色标志位 例如 你可以用 1 来表示 黑色
density = 0 # 色块密度比 反映色块锁定程度 值越大 锁定程度越好
pixels_max = 0 # 色块像素最大值
led_flag = 0 # LED标志位 方便调试用
color_threshold = (0, 0, 0, 0, 0, 0) # 色块颜色阈值
color_roi = (0,0,320,240) # 色块寻找区域(感兴趣区域)
color_x_stride = 1 # 色块 x轴 像素最小宽度 色块如果比较大可以调大此参数 提高寻找速度
color_y_stride = 1 # 色块 y轴 像素最小宽度 色块如果比较大可以调大此参数 提高寻找速度
color_pixels_threshold = 100 # 色块 像素个数阈值 例如调节此参数为100 则可以滤除色块像素小于100的色块
color_area_threshold = 100 # 色块 被框面积阈值 例如调节此参数为100 则可以滤除色块被框面积小于100的色块
color_merge = True # 是否合并寻找到的色块 True 则合并 False 则不合并
color_margin = 1 # 色块合并间距 例如调节此参数为1 若上面选择True合并色块 且被找到的色块有多个 相距1像素 则会将这些色块合并
# 实例化类
# 黑色
black = color_property()
black.color_threshold = (0, 50, -10, 10, -10, 10)
black.color_roi = (0,0,320,240)
black.color_x_stride = 1
black.color_y_stride = 1
black.color_pixels_threshold = 100
black.color_area_threshold = 100
black.color_merge = True
black.color_margin = 1
# 红色
red = color_property()
red.color_threshold = (0, 100, 20, 127, -10, 127)
#red.color_roi = (0,0,320,240)
red.color_roi = (0,110,320,20)
red.color_x_stride = 1
red.color_y_stride = 1
#red.color_pixels_threshold = 100
#red.color_area_threshold = 100
red.color_pixels_threshold = 10
red.color_area_threshold = 10
red.color_merge = True
red.color_margin = 1
# 绿色 预留
green = color_property()
# 蓝色 预留
blue = color_property()
# 定义寻找色块函数
def opv_find_blobs(color,led_flag):
color.pixels_max = 0 # 重置 色块 最大像素数量
color.flag = 0 # 重置 色块 标志位
color.led_flag = 0 # 重置 led 标志位
for blobs in img.find_blobs([color.color_threshold], # 色块颜色阈值
roi = color.color_roi, # 色块寻找区域(感兴趣区域)
x_stride = color.color_x_stride, # 色块 x轴 像素最小宽度 色块如果比较大可以调大此参数 提高寻找速度
y_stride = color.color_y_stride, # 色块 y轴 像素最小宽度 色块如果比较大可以调大此参数 提高寻找速度
pixels_threshold = color.color_pixels_threshold, # 色块 像素个数阈值 例如调节此参数为100 则可以滤除色块像素小于100的色块
area_threshold = color.color_area_threshold, # 色块 被框面积阈值 例如调节此参数为100 则可以滤除色块被框面积小于100的色块
merge = color.color_merge, # 是否合并寻找到的色块 True 则合并 False 则不合并
margin = color.color_margin): # 色块合并间距 例如调节此参数为1 若上面选择True合并色块 且被找到的色块有多个 相距1像素 则会将这些色块合并
img.draw_rectangle(blobs[0:4]) # 圈出找到的色块
if color.pixels_max < blobs.pixels(): # 找到面积最大的色块
color.pixels_max = blobs.pixels()
color.cx = blobs.cx() # 将面积最大的色块的 x轴 中心坐标值 赋值给 color
color.cy = blobs.cy() # 将面积最大的色块的 y轴 中心坐标值 赋值给 color
color.flag = 1 # 标志画面中有找到色块
color.density = blobs.density() # 将面积最大的色块的 色块密度比 赋值给 color
color.led_flag = led_flag # 将控制led颜色的标志位的值 赋值给 color
if color.flag == 1: # 标记画面中被找到的最大色块的中心坐标
img.draw_cross(color.cx,color.cy, color=127, size = 15)
img.draw_circle(color.cx,color.cy, 15, color = 127)
# 定义打印色块参数函数
def print_blobs_property(color,name):
print(name,"cx:",color.cx,"cy:",color.cy,"flag:",color.flag,"color:",color.color,"density:",color.density,"led_flag:",color.led_flag)
#__________________________________________________________________
# 打印sensor参数
def print_sensor():
print("Exposure: "+str(sensor.get_exposure_us())) # 打印 曝光时间
print("Gain: "+str(sensor.get_gain_db())) # 打印 画面增益
print("RGB: "+str(sensor.get_rgb_gain_db())) # 打印 RGB 增益
#__________________________________________________________________
# 调试区
mycnt = 0 # 计数变量
mycnt_max = 30 # 计数上限 此值越大 计数周期越长
#__________________________________________________________________
# 主函数
while(True):
clock.tick() # 跟踪运行时间
img = sensor.snapshot() # 拍摄一张照片
opv_find_blobs(black,1) # 找黑色色块 led标志为1 表示黑色
opv_find_blobs(red,2) # 找红色色块 led标志为2 表示红色
if mycnt == 0: # 如果 mycnt 等于 0 此步骤的目的是控制打印周期 不要打印的太快
mycnt = 1 # 将 1 赋值给 mycnt 使下一次不再满足 mycnt == 0 进入 elif
print_sensor() # 打印sensor参数
print_blobs_property(black,"Black-") # 打印黑色色块参数
print_blobs_property(red, "Red- ") # 打印红色色块参数
elif mycnt < mycnt_max: # 计数变量 小于 计数上限 则 计数变量 自增
mycnt = mycnt + 1
else: # 计数变量 超出 计数上限 则 将0赋值给 mycnt 使下一次进入 if
mycnt = 0
#__________________________________________________________________