王嘉辉 发表于 2024-10-9 00:51

【嘉楠科技 CanMV K230测评】机器视觉基础——摄像头、显示以及画图

<p><strong>Camera &amp; Display</strong></p>

<p>完成了K230基本外设的学习,下面才是真正能够发挥K230实力的部分。机器视觉以及AI相关的内容。</p>

<p>机器视觉首先最为重要的便是采集图像,为了方便我们进行调试,将采集到的图像进行实时的显示出来也是十分重要的。因此首先学习一下怎么在K230上使用摄像头进行图像采集,并将采集到的图像输出进行显示。</p>

<p>在K230中的摄像头相关的函数放在Sensor模块中,因此在使用的时候,首先要引入Sensor模块。K230有三个摄像头通道,每个摄像头都可以独立的完成图像的采集,并可以同时输出3路图像数据。其框架如下图所示。</p>

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

<p>构建函数,调用函数sensor = Sensor(id, )进行构建。其中id为csi端口的选择,可选择的范围是0-2,在K230的开发板上带的摄像头的csi为2。width为最大输出图像的宽度。height为最大输出图像的高度。fps为最大输出图像的帧率。后三个参数的默认值为1920、1080、30。</p>

<p>复位函数sensor.reset()。在构建摄像头对象后,必须调用该函数,才能继续其他的操作。</p>

<p>在构建函数中的后三个参数都可以通过调用单独的函数进行更改。首先是输出图像的尺寸大小,可以使用函数sensor.set_framesize(framesize = FRAME_SIZE_INVAILD, chn = CAM_CHN_ID_0, alignment=0, **kwargs)进行输出图像尺寸的选择。</p>

<p>sensor.set_pixformat(pix_format, chn = CAM_CHN_ID_0)函数可以更改输出的图形格式。</p>

<p>sensor.set_hmirror(enable)用于设置图像的水平镜像,sensor.set_vflip(enable)用于设置图像的垂直翻转。</p>

<p>sensor.run()函数让摄像头开始输出,根据API手册,该函数必须在MediaManager.init()之前进行调用。sensor.stop()函数让摄像头停止输出,根据API手册,该函数必须在MediaManager.deinit()之前调用。注意!!!如果在使用多个摄像头的时候,run函数仅需要调用任意一个即可,stop函数需要每个都调用。</p>

<p>sensor.snapshot(chn = CAM_CHN_ID_0)用于获取一帧图像数据。</p>

<p>sensor.bind_info(x = 0, y = 0, chn = CAM_CHN_ID_0)用于将输出的图像绑定到Display的指定坐标。</p>

<p>&nbsp;</p>

<p>在K230中关于图像显示这一块的内容主要存放在Display模块中,因此在使用的时候,首先要引入Display模块。K230的图像输出方式有三种,分别是HDMI、MIPI以及IDE内部缓冲区显示,三种方式相比IDE内部缓冲区成本较低,且调试方便,因此主要以IDE内部缓冲区进行介绍。</p>

<p>显示模块的初始化调用函数def init(type = None, width = None, height = None, osd_num = 1, to_ide = False, fps = None, quality = 90)其中参数type为必选项,内容为显示设备的类型,在这里我们选择使用VIRT即IDE内置的缓冲区域。参数width和height为显示图像的宽度和高度,这两个选项的默认值会根据type的改变而改变,在VIRT类型下,这两个值的默认值为640和480。osd_num为在show_image时可以支持的LAYER数量,跟默认保持一致,选择为1。to_ide为是否选择将图像同步显示在IDE内置的缓冲区中。fps为是否显示帧率。quality为设置IDE缓冲区显示的图像质量。 因为显示的方式仅有IDE内置显示,所以其中type只能选择VIRT,width和height根据默认分别选择为640和480,其余的可以保持默认,不进行配置。</p>

<p>图像的显示使用函数def show_image(img, x = 0, y = 0, layer = None, alpha = 255, flag = 0),其中参数img为摄像头拍摄的图片的信息,x和y为显示的起点坐标,layer为显示到指定的层。alpha为图层混合的参数,flag为显示标志。在使用的时候,主要就是使用前三个参数即可实现图像的显示。</p>

<p>def deinit()函数用于进行Display的反初始化,这个模块的使用会关闭Display的所有通路。其使用条件必须在MediaManager.deinit()之前调用且必须在sensor.stop()之后调用。</p>

<p>def bind_layer(src=(mod, dev, layer), dstlayer, rect = (x, y, w, h), pix_format, alpha, flag)函数用于将sensor的图像直接输出到display中,不需要用户手动参与即可将图像持续的显示在屏幕上(感觉有点像DMA进行数据的传输)。其中的参数,src为输出信息,可以通过调用函数sensor.bind_info()进行获取。dstlayer为绑定到display的指定层。rect为显示的区域。pix_format为图像像素格式,alpha为图层混合,flag为显示标志。</p>

<p>在K230中,摄像头和图像显示之间还有一个软件抽象层,叫做media模块,主要是针对K230 CanMV平台媒体数据链路以及媒体缓冲区相关操作的封装。其相关的API接口可以参考官方提供的API手册<a href="https://developer.canaan-creative.com/k230_canmv/main/zh/api/mpp/K230_CanMV_Media%E6%A8%A1%E5%9D%97API%E6%89%8B%E5%86%8C.html" target="_blank">3.4 Media模块API手册 &mdash; K230 CanMV</a></p>

<p>首先是初始化和反初始化的函数,MediaManager.init()和MediaManager.deinit()。</p>

<p>配置的函数是MediaManager._config(config)。其中config参数为媒体缓冲区配置参数。</p>

<p>用于将输入和输出的连接的函数是MediaManager.link(src=(mod,dev,chn), dst = (mod,dev,chn))。其中src为输入源,dst为输出。</p>

<p>&nbsp;</p>

<p>这三个模块的配合使用可以实现K230的摄像头采集图像,并在IDE上进行显示。下面结合代码进行详细的过程分析。</p>

<pre>
<code class="language-python">import time, os, sys

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

try:

    sensor = Sensor() #构建摄像头对象
    sensor.reset() #复位和初始化摄像头
    sensor.set_framesize(Sensor.FHD) #设置帧大小FHD(1920x1080),默认通道0
    sensor.set_pixformat(Sensor.RGB565) #设置输出图像格式,默认通道0

    #使用IDE缓冲区输出图像,显示尺寸和sensor配置一致。
    Display.init(Display.VIRT, sensor.width(), sensor.height())

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

    sensor.run() #启动sensor

    clock = time.clock()

    while True:


      os.exitpoint() #检测IDE中断

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

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

      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>实现图像的采集和显示的代码,相比于之前单纯的外设内容多了很多。下面尽可能进行逐行的分析。</p>

<p>首先引入了time、os、sys相关的模块。然后引入摄像头和图像显示相关的部分,media.sensor、media.display、media.media。</p>

<p>首先构建摄像头的函数sensor,然后对摄像头进行复位和初始化,设置显示大小和显示格式。</p>

<p>然后进行显示部分的初始化,配置参数为IDE显示,宽度和高度与sensor的宽度和高度保持一致。</p>

<p>然后初始化MediaManager。</p>

<p>完成三个模块的初始化和参数配置后,启动摄像头,使用sensor.run函数。</p>

<p>创建一个clock函数,主要用于fps的计算。</p>

<p>在循环中,拍摄图像前进行一次clock的tick获取。然后拍摄一次,并将数据传入到img变量中,使用display模块将拍摄的图像进行显示。调用clock中的fsp函数,并将返回值进行打印显示。</p>

<p>这样就可以实现图像的采集和显示。</p>

<p>下面的代码,是当上面的代码出现问题后,进行报错打印以及相关操作停止的代码。重点看一下最后的关闭的顺序。</p>

<p>首先进行摄像头sensor的停止,然后对display进行反初始化,最后再进行MediaManager的反初始化。</p>

<p>下面进行视频的演示。在视频中可以看到,使用摄像头拍摄的外面的画面,并在IDE中进行了显示。同时在下面的串行终端中进行了帧率的打印。</p>

<p>bea48a647fcad4660c52c4370d1849d9<br />
<strong>Drawing</strong></p>

<p>在图像识别中,完成图像的采集和显示后,往往会在显示的图像上做一些标记,从而实现对目标的追踪和锁定。因此,就需要我们在图像上进行画图的操作。</p>

<p>在画图中,我们操作的主要对象是image对象,image是机器视觉中最基本的对象,因为后面进行的操作几乎都是基于image进行处理。所以要先简单介绍一下image对象,后面再逐渐深入去了解相关的API。</p>

<p>在摄像头相关内容中,会使用sensor.snapshot进行一帧图像的获取,并返回image对象。此时我们就获取了image对象。然后可以针对这个image对象进行一系列的画线、画圆圈、画方块等等的操作,这些操作被夹在拍摄一帧照片和进行显示之间。当然这只是一种创建image对象的方式,也可以新建一个image对象或者从从本地路径下去读取一张照片进行显示。这样的操作方式为:img=image.Image(w, h, format)和img=image.Image(path[, copy_to_fb=False])。其对应的例子为img = image.Image(640, 480, image.RGB565)和img = image.Image(&quot;01Studio.bmp&quot;, copy_to_fb=True)。其中format为图像格式,大部分都选择RGB565格式。copy_to_fb为是否加载大图片,可选项为True和False。</p>

<p>下面重点介绍一下画图的一些相关操作。</p>

<p>画线:image.draw_line(x0, y0, x1, y1[, color[, thickness=1]])参数依次为起点横坐标、起点纵坐标、终点横坐标、终点纵坐标、颜色、颜色粗细。</p>

<p>画矩形:image.draw_rectangle(x, y, w, h[, color[, thickness=1[, fill=False]]])参数依次为起点横坐标、起点纵坐标、矩形宽度、矩形高度、颜色、边框粗细、是否填充。</p>

<p>画圆形:image.draw_circle(x, y, radius[, color[, thickness=1[, fill=False]]])参数依次为圆心横坐标、圆心纵坐标、半径、颜色、线条粗细、是否填充。</p>

<p>画箭头:image.draw_arrow(x0, y0, x1, y1[, color[, size,]])参数依次为起点横坐标、起点纵坐标、终点横坐标、终点纵坐标、颜色、箭头位置大小、线条粗细。</p>

<p>画十字:image.draw_cross(x, y[, color[, size=5[, thickness=1]]])参数依次为中点横坐标、中点纵坐标、颜色、大小、线条粗细。</p>

<p>写字符:image.draw_string(x, y, text[, color[, scale=1[,mono_space=True&hellip;]]]])参数依次为起点横坐标、起点纵坐标、文本内容、颜色、大小、是否有间隔。</p>

<p>写中文字符:image.draw_string_advanced(x, y, char_size,str,)参数依次为起点横坐标、起点纵坐标、字符大小、字符串信息、颜色、字体类型。</p>

<p>下面使用代码实现在采集的图像上进行一些简单的绘制操作。</p>

<pre>
<code class="language-python">import time, os, sys

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

try:

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

    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()
      # 画线段:从 x0, y0 到 x1, y1 坐标的线段,颜色红色,线宽度 2。
      img.draw_line(20, 20, 100, 20, color = (255, 0, 0), thickness = 2)

      #画矩形:绿色不填充。
      img.draw_rectangle(150, 20, 100, 30, color = (0, 255, 0), thickness = 2, fill = True)

      #画圆:蓝色不填充。
      img.draw_circle(60, 120, 30, color = (0, 0, 255), thickness = 2, fill = False)

      #画箭头:白色。
      img.draw_arrow(150, 120, 250, 120, color = (255, 255, 255), size = 20, thickness = 2)

      #画十字交叉。
      img.draw_cross(60, 200, color = (255, 255, 255), size = 20, thickness = 2)

      #写字符,支持中文。
      img.draw_string_advanced(150, 180, 30, "EEWORLD", color = (255, 255, 255))
      img.draw_string_advanced(40, 300, 30, "电子工程世界", 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>大体的代码和上一节的内容保持一致,只是在img = sensor.snapshot()和Display.show_image(img)之间对img对象进行了一些操作。</p>

<p>即画线段、画矩形、画圆、画箭头、画十字交叉以及字符的显示。</p>

<p>演示视频如下:</p>

<p>270c56c8908ce02eb22046d0eb79ae8f<br />
&nbsp;</p>

秦天qintian0303 发表于 2024-10-10 23:27

<p>怎么进行分类训练啊?手动圈吗?&nbsp;&nbsp;</p>

王嘉辉 发表于 2024-10-11 09:53

秦天qintian0303 发表于 2024-10-10 23:27
怎么进行分类训练啊?手动圈吗?&nbsp;&nbsp;

<p>还没学习到那一步,但是之前看了一个例程,不知道符不符合您的意思。<a href="https://wiki.01studio.cc/docs/canmv_k230/machine_vision/ai_vision/self_learning">自分类学习 | 01Studio</a></p>

nmg 发表于 2024-10-12 16:26

王嘉辉 发表于 2024-10-11 09:53
还没学习到那一步,但是之前看了一个例程,不知道符不符合您的意思。自分类学习 | 01Studio

<p><img height="50" src="https://bbs.eeworld.com.cn/static/editor/plugins/hkemoji/sticker/facebook/wanwan21.gif" width="63" />期待继续的学习分享</p>
页: [1]
查看完整版本: 【嘉楠科技 CanMV K230测评】机器视觉基础——摄像头、显示以及画图