dfjs 发表于 2024-11-3 21:28

嘉楠K230AI开发板测评5---颜色识别、摄像头物体计数与巡线

本帖最后由 dfjs 于 2024-11-3 21:29 编辑

<p align="center"><b>嘉楠科K230AI开发板</b><b>测评5--机器视觉篇</b></p>

<p><strong>1.单一颜色识别</strong></p>

<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;预先设定颜色阈值,如红、绿、蓝,这样K230摄像头采集图像后就能自动识别了。</p>

<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;CanMV集成了RGB565颜色块识别find_blobs函数(其位于image模块下),主要是基于LAB(L:亮度,取值0-100,表示从纯黑到纯白的变化;A代表从绿色到红色的范围,取值是-128--127;B代表从蓝色到黄色的范围,取值是-128--127)颜色模型,每个颜色都是用一组LAB阈值表示。</p>

<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;image.find_blobs(thresholds[, invert=False[, roi[, x_stride=2[, y_stride=1[, area_threshold=10 [, pixels_threshold=10[, merge=False[, margin=0[, threshold_cb=None[, merge_cb=None]]]]]]]]]])函数查找图像中指定的色块,返回image.blog对象列表。</p>

<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;thresholds: 必须是元组列表。 [(lo, hi), (lo, hi), ..., (lo, hi)] 定义你想追踪的颜色范围。 对于灰度图像,每个元组需要包含两个值 - 最小灰度值和最大灰度值。 仅考虑落在这些阈值之间的像素区域。 对于RGB565图像,每个元组需要有六个值(l_lo,l_hi,a_lo,a_hi,b_lo,b_hi) - 分别是LAB L,A和B通道的最小值和最大值;</p>

<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;area_threshold: 若色块的边界框区域小于此参数值,则会被过滤掉;</p>

<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;pixels_threshold: 若色块的像素数量小于此参数值,则会被过滤掉;</p>

<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;merge: 若为True,则合并所有没有被过滤的色块;</p>

<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;margin: 调整合并色块的边缘。</p>

<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;blob.rect()函数返回一个矩形元组(x,y,w,h),如色块边界。可以通过索引来获得这些值。</p>

<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;blob.cx()返回色块(int)的中心x位置。可以通过索引来获得这个值。</p>

<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;blob.cy()返回色块(int)的中心y位置。可以通过索引来获得这个值。</p>

<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;代码编写流程如下:</p>

<p align="center"> &nbsp;</p>

<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;参考代码:</p>

<pre>
<code class="language-python">'''
实验名称:单一颜色识别
实验平台:01Studio CanMV K230
教程:wiki.01studio.cc
'''

import time, os, sys

from media.sensor import * #导入sensor模块,使用摄像头相关接口
from media.display import * #导入display模块,使用display相关接口
from media.media import * #导入media模块,使用meida相关接口

# 颜色识别阈值 (L Min, L Max, A Min, A Max, B Min, B Max) LAB模型
# 下面的阈值元组是用来识别 红、绿、蓝三种颜色,当然你也可以调整让识别变得更好。
thresholds = [(30, 100, 15, 127, 15, 127), # 红色阈值
            (30, 100, -64, -8, 50, 70), # 绿色阈值
            (0, 40, 0, 90, -128, -20)] # 蓝色阈值
            
try:

    sensor = Sensor() #构建摄像头对象
    sensor.reset() #复位和初始化摄像头
    sensor.set_framesize(width=800, height=480) #设置帧大小为LCD分辨率(800x480),默认通道0
    sensor.set_pixformat(Sensor.RGB565) #设置输出图像格式,默认通道0

    Display.init(Display.ST7701, to_ide=True) #同时使用3.5寸mipi屏和IDE缓冲区显示图像,800x480分辨率
    #Display.init(Display.VIRT, sensor.width(), sensor.height()) #只使用IDE缓冲区显示图像

    MediaManager.init() #初始化media资源管理器

    sensor.run() #启动sensor

    clock = time.clock()

    while True:


      os.exitpoint() #检测IDE中断

      ################
      ## 这里编写代码 ##
      ################
      clock.tick()

      img = sensor.snapshot() #拍摄一张图片

      blobs = img.find_blobs(]) # 0,1,2分别表示红,绿,蓝色。

      if blobs:

            for b in blobs: #画矩形和箭头表示
                tmp=img.draw_rectangle(b, thickness = 2)
                tmp=img.draw_cross(b, b, thickness = 2)

      img.draw_string_advanced(0, 0, 30, 'FPS: '+str("%.3f"%(clock.fps())), color = (255, 255, 255))

      Display.show_image(img) #显示图片

      print(clock.fps()) #打印FPS


###################
# IDE中断释放资源代码
###################
except KeyboardInterrupt as e:
    print("user stop: ", e)
except BaseException as e:
    print(f"Exception {e}")
finally:
    # sensor stop run
    if isinstance(sensor, Sensor):
      sensor.stop()
    # deinit display
    Display.deinit()
    os.exitpoint(os.EXITPOINT_ENABLE_SLEEP)
    time.sleep_ms(100)
    # release media buffer
    MediaManager.deinit()</code></pre>

<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;实验结果,如下图1,也可通过阈值编辑器来手动调节LAB的阈值范围,如下图2。</p>

<p style="text-align: center;"></p>

<p style="text-align: center;">&nbsp; &nbsp;</p>

<p><strong>2.多种颜色识别</strong></p>

<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;基于单一颜色识别,加以修改,即可实现多种颜色识别。</p>

<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;代码编写流程如下:</p>

<p style="text-align: center;">&nbsp;</p>

<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;核心代码如下,与单一颜色识别例程相比,修改的代码如下,在颜色识别前中加入了for循环,识别预设的3种颜色:</p>

<pre>
<code class="language-python"># 颜色识别阈值 (L Min, L Max, A Min, A Max, B Min, B Max) LAB模型
# 下面的阈值元组是用来识别 红、绿、蓝三种颜色,当然你也可以调整让识别变得更好。
thresholds = [(30, 100, 15, 127, 15, 127), # 红色阈值
            (30, 100, -64, -8, 50, 70), # 绿色阈值
            (0, 40, 0, 90, -128, -20)] # 蓝色阈值

colors1 = [(255,0,0), (0,255,0), (0,0,255)]
colors2 = ['RED', 'GREEN', 'BLUE']

..............


      img = sensor.snapshot() #拍摄一张图片
      for i in range(3):
            blobs = img.find_blobs(]) # 0,1,2分别表示红,绿,蓝色。
            if blobs:

                for b in blobs: #画矩形、箭头和字符表示
                  tmp=img.draw_rectangle(b, thickness = 4, color = colors1)
                  tmp=img.draw_cross(b, b, thickness = 2)
                  tmp=img.draw_string_advanced(b, b-35, 30, colors2,color = colors1)


      img.draw_string_advanced(0, 0, 30, 'FPS: '+str("%.3f"%(clock.fps())), color = (255, 255, 255))

      Display.show_image(img) #显示图片

      print(clock.fps()) #打印FPS</code></pre>

<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;实验结果如下,将每个颜色的圆形用矩形画出并表明颜色,与单一颜色识别例程相比,修改的代码如下,在颜色识别前中加入了for循环,识别预设的3种颜色:</p>

<p style="text-align: center;"> &nbsp;</p>

<p><strong>3.物体计数(相同颜色)</strong></p>

<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;基于上一节颜色识别我们看到可以识别出色块的数量,来学习如何识别指定颜色的物体,计算其数量。</p>

<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;针对不同颜色的物体我们如何获取它的阈值呢?</p>

<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;先使用 摄像头代码采集物体图像,在IDE右上角缓冲区点击&ldquo;禁用&rdquo;将要识别的物体确认下来;点击 工具&mdash;机器视觉&mdash;阈值编辑器&nbsp;。在弹出的对话框选择&ldquo;帧缓冲区&rdquo;。通过调整下方6个LAB值,使得物体颜色在右边为白色,其余背景为黑色。记录颜色的LAB值,在后面代码中使用,如下图。</p>

<p style="text-align: center;"> &nbsp;</p>

<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;代码编写流程如下图:</p>

<p style="text-align: center;"> &nbsp;</p>

<p align="justify">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;核心代码如下:</p>

<pre>
<code class="language-python">.............
thresholds = [(18, 72, -13, 31, 18, 83)] #黄色跳线帽阈值
.............

      img = sensor.snapshot()
      blobs = img.find_blobs(])

      if blobs: #画框显示
            for b in blobs:
                tmp=img.draw_rectangle(b)
                tmp=img.draw_cross(b, b)

      #显示计算信息
      img.draw_string_advanced(0, 0, 30, 'FPS: '+str("%.3f"%(clock.fps()))+'       Num: '
                                 +str(len(blobs)), color = (255, 255, 255))

      Display.show_image(img)

      print(clock.fps()) #打印FPS
.............</code></pre>

<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;实验结果如下图,在阈值准确的情况下,统计出跳线帽的数量:</p>

<p style="text-align: center;"> &nbsp;</p>

<p><strong>4.机器人巡线(实线)</strong></p>

<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;机器人巡线依然基于颜色识别,根据摄像头采集到的图像直线与中心偏离的位置计算出偏离角度。</p>

<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;对画面是有一定要求的,也就是摄像头采集图像一定要出现唯一1条连续的黑色直线。程序通过对画面切割成三部分,计算每个部分黑色线的中心点X坐标,然后采用加权平均算法估算出直线的偏离位置。通常情况下越靠近底部的地方离摄像头越近,顶部表示远方线段。因此底部的图形权重高。</p>

<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;假设摄像头当前画面的像素是例程的QQVGA分辨率:160(宽)X120(高),左上角坐标为(0,0),然后当前出现直线坐标为(80,120)至(160,0)偏右的直线。上中下三个部分的权重分别为0.1、0.3、0.7(底部图像靠近机器人,权重大,权重总和可以不是1),我们来计算一下其中心值,如下图:</p>

<p style="text-align: center;"> &nbsp;</p>

<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;上图中Y轴的中点坐标就是60,X坐标加权平均值计算如下:</p>

<p align="center">X=(80*0.7+120*0.3+160*0.1)/(0.7+0.3+0.1)=98</p>

<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;那么直线偏离坐标可以认为是(98,60),图中绿色&ldquo;+&rdquo;位置。那么利用反正切函数可以求出偏离角度:a = atan((98-80)/60)=16.7&deg;,机器人相当于实线的位置往左偏了,所以加一个负号,即 -16.7&deg;;偏离角度就是这么计算出来的。得到偏离角度后就可以自己编程去调整小车或者机器人的运动状态,直到0&deg;为没有偏离。</p>

<p>代码编写思路如下:</p>

<p style="text-align: center;"> &nbsp;</p>

<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;参考代码如下:</p>

<pre>
<code class="language-python">'''
实验名称:机器人巡线(实线)
实验平台:01Studio CanMV K230
教程:wiki.01studio.cc

# 黑色灰度线巡线跟踪示例
#
#做一个跟随机器人的机器人需要很多的努力。这个示例脚本
#演示了如何做机器视觉部分的线跟随机器人。你
#可以使用该脚本的输出来驱动一个差分驱动机器人
#跟着一条线走。这个脚本只生成一个表示的旋转值(偏离角度)
#你的机器人向左或向右。
#
# 为了让本示例正常工作,你应该将摄像头对准一条直线(实线)
#并将摄像头调整到水平面45度位置。请保证画面内只有1条直线。
'''

import time, os, sys, math

from media.sensor import * #导入sensor模块,使用摄像头相关接口
from media.display import * #导入display模块,使用display相关接口
from media.media import * #导入media模块,使用meida相关接口

# 追踪黑线。使用 [(128, 255)] 追踪白线.
GRAYSCALE_THRESHOLD = [(0, 64)]

# 下面是一个roi【区域】元组列表。每个 roi 用 (x, y, w, h)表示的矩形。

'''
#采样图像QQVGA 160*120,列表把roi把图像分成3个矩形,越靠近的摄像头视野(通常为图像下方)的矩形权重越大。
ROIS = [ #
      (0, 100, 160, 20, 0.7), # 可以根据不同机器人情况进行调整。
      (0,50, 160, 20, 0.3),
      (0,   0, 160, 20, 0.1)
       ]
'''
#采样图像为QVGA 320*240,列表把roi把图像分成3个矩形,越靠近的摄像头视野(通常为图像下方)的矩形权重越大。
ROIS = [ #
      (0, 200, 320, 40, 0.7), # 可以根据不同机器人情况进行调整。
      (0,100, 320, 40, 0.3),
      (0,   0, 320, 40, 0.1)
       ]

# 计算以上3个矩形的权值【weight】的和,和不需要一定为1.
weight_sum = 0
for r in ROIS: weight_sum += r # r 为矩形权重值.

try:

    sensor = Sensor(width=1280, height=960) #构建摄像头对象,将摄像头长宽设置为4:3
    sensor.reset() #复位和初始化摄像头
    sensor.set_framesize(width=320, height=240) #设置帧大小,默认通道0
    sensor.set_pixformat(Sensor.GRAYSCALE) #设置输出图像格式,默认通道0

    Display.init(Display.ST7701, to_ide=True) #同时使用3.5寸mipi屏和IDE缓冲区显示图像,800x480分辨率
    #Display.init(Display.VIRT, sensor.width(), sensor.height()) #只使用IDE缓冲区显示图像

    MediaManager.init() #初始化media资源管理器

    sensor.run() #启动sensor

    clock = time.clock()

    while True:


      os.exitpoint() #检测IDE中断

      ################
      ## 这里编写代码 ##
      ################
      clock.tick()

      img = sensor.snapshot() #拍摄一张图片

      centroid_sum = 0

      for r in ROIS:
            blobs = img.find_blobs(GRAYSCALE_THRESHOLD, roi=r, merge=True) # r 是上面定义的roi元组.

            if blobs:
                # Find the blob with the most pixels.
                largest_blob = max(blobs, key=lambda b: b.pixels())

                # Draw a rect around the blob.
                img.draw_rectangle(largest_blob.rect())
                img.draw_cross(largest_blob.cx(),
                               largest_blob.cy())

                centroid_sum += largest_blob.cx() * r # r 是每个roi的权重值.

      center_pos = (centroid_sum / weight_sum) # 确定直线的中心.

      # 将直线中心位置转换成角度,便于机器人处理.
      deflection_angle = 0

      # 使用反正切函数计算直线中心偏离角度。可以自行画图理解
      #权重X坐标落在图像左半部分记作正偏,落在右边部分记为负偏,所以计算结果加负号。

      #deflection_angle = -math.atan((center_pos-80)/60) #采用图像为QQVGA 160*120时候使用

      deflection_angle = -math.atan((center_pos-160)/120) #采用图像为QVGA 320*240时候使用

      # 将偏离值转换成偏离角度.
      deflection_angle = math.degrees(deflection_angle)

      # 计算偏离角度后可以控制机器人进行调整.
      print("Turn Angle: %f" % deflection_angle)

      # LCD显示偏移角度,scale参数可以改变字体大小
      img.draw_string_advanced(2,2,20, str('%.1f' % deflection_angle), color=(255,255,255))

      #Display.show_image(img) #显示图片

      #显示图片,仅用于LCD居中方式显示
      Display.show_image(img, x=round((800-sensor.width())/2),y=round((480-sensor.height())/2))

      print(clock.fps()) #打印FPS

###################
# IDE中断释放资源代码
###################
except KeyboardInterrupt as e:
    print("user stop: ", e)
except BaseException as e:
    print(f"Exception {e}")
finally:
    # sensor stop run
    if isinstance(sensor, Sensor):
      sensor.stop()
    # deinit display
    Display.deinit()
    os.exitpoint(os.EXITPOINT_ENABLE_SLEEP)
    time.sleep_ms(100)
    # release media buffer
    MediaManager.deinit()</code></pre>

<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;实验结果如下,以手机做黑色直线用,分别观察摄像头采集到没偏移、左偏和右偏各个直线的实验结果,可以看出效果良好。手机偏移角度分别为接近0&deg;,负数,正数。获取到的偏移角度可以通过串口发送给其他外设或者主控。</p>

<p style="text-align: center;"></p>

<p style="text-align: center;">&nbsp; &nbsp;</p>

<p style="text-align: center;"> &nbsp;</p>
页: [1]
查看完整版本: 嘉楠K230AI开发板测评5---颜色识别、摄像头物体计数与巡线