1041|3

27

帖子

1

TA的资源

一粒金砂(中级)

楼主
 

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

 

Camera & Display

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

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

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

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

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

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

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

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

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

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

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

 

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

显示模块的初始化调用函数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,其余的可以保持默认,不进行配置。

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

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

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为显示标志。

在K230中,摄像头和图像显示之间还有一个软件抽象层,叫做media模块,主要是针对K230 CanMV平台媒体数据链路以及媒体缓冲区相关操作的封装。其相关的API接口可以参考官方提供的API手册3.4 Media模块API手册 — K230 CanMV

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

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

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

 

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

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()

实现图像的采集和显示的代码,相比于之前单纯的外设内容多了很多。下面尽可能进行逐行的分析。

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

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

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

然后初始化MediaManager。

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

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

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

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

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

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

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

Camera&Display

Drawing

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

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

在摄像头相关内容中,会使用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("01Studio.bmp", copy_to_fb=True)。其中format为图像格式,大部分都选择RGB565格式。copy_to_fb为是否加载大图片,可选项为True和False。

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

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

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

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

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

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

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

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

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

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()

大体的代码和上一节的内容保持一致,只是在img = sensor.snapshot()和Display.show_image(img)之间对img对象进行了一些操作。

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

演示视频如下:

Drawing

 

最新回复

nmg
期待继续的学习分享   详情 回复 发表于 2024-10-12 16:26
点赞 关注
 
 

回复
举报

6450

帖子

10

TA的资源

版主

沙发
 

怎么进行分类训练啊?手动圈吗?  

点评

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

在爱好的道路上不断前进,在生活的迷雾中播撒光引

 
 
 

回复

27

帖子

1

TA的资源

一粒金砂(中级)

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

还没学习到那一步,但是之前看了一个例程,不知道符不符合您的意思。自分类学习 | 01Studio

点评

nmg
期待继续的学习分享  详情 回复 发表于 2024-10-12 16:26
 
 
 

回复

5260

帖子

236

TA的资源

管理员

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

期待继续的学习分享

加EE小助手好友,
入技术交流群
EE服务号
精彩活动e手掌握
EE订阅号
热门资讯e网打尽
聚焦汽车电子软硬件开发
认真关注技术本身
 
 
 

回复
您需要登录后才可以回帖 登录 | 注册

随便看看
查找数据手册?

EEWorld Datasheet 技术支持

相关文章 更多>>
推荐帖子
一个ARM7的手册,全部的语言格式和用法

一个ARM7的手册,全部的语言格式和用法

LPC1343学习笔记(连载中)--6月21日新增第十二篇

有幸拿到了EEWORLD论坛的LPC1343评估板,实在是一件意外而激励人心的事情。为感谢EEWORLD和NXP,特将学习过程与大家共同分享。也 ...

2011 TI M3 DAY资料提前放出

明天TI M3 DAY就要开始了,提前放出相关资料,感兴趣的朋友可以预习一下。 本帖最后由 jkhu 于 2011-6-19 23:19 编辑 ]

我的Beaglebone学习历程

整理一下前面发的帖子,搞个总帖,方便大家交流。1.BeagleBone 硬件性能测试 _周计划https://bbs.eeworld.com.cn/thread-324885- ...

GD32F105RBT6和 STM32F103RBT6是完全兼容的,程序也完全兼容吗?

GD32F105RBT6和 STM32F103RBT6是完全兼容的,一个引脚都不差,,,下载程序的接口也一样的,,,是不是可以直接使用原来STM32里 ...

【Silicon Labs 开发套件评测】+SPI flash(MX25R8035F)

在很多应用中,需要保存配置参数。一般都会外接一个存储器来存储,数据比较少会使用EEPROM,使用SPI flash的也比较多,存储空间 ...

【基于NUCLEO-F746ZG电机开发应用】12.参数配置-定时器TIM1配置

在伺服电机的控制过程中,使电机能够按照自己的想法转起来,一定要用到PWM输出控制,但是PWM该如何产生,频率如何控制,占空比 ...

瑞萨CPK-RA6M4 开发板测评----I2C

功能模块的硬件介绍-->I2C I2C(Inter Integrated Circuit)总线是 PHILIPS 公司开发的一种半双工、双向二线制同步串 ...

二极管常用的8个用途

之前有个版本是7中用途 二极管是十分常用的基础元器件,本文主要介绍了二极管的一些作用,比如防反、整流、稳压、续流、检波 ...

visionfive的星光2开发板移植openwrt上篇--编译篇

本帖最后由 怀揣少年梦 于 2023-8-15 23:57 编辑 ###一、openwrt是什么? openwrt是一个常用于路由器嵌入式linux操作系统 ...

关闭
站长推荐上一条 1/9 下一条

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

About Us 关于我们 客户服务 联系方式 器件索引 网站地图 最新更新 手机版

站点相关: 国产芯 安防电子 汽车电子 手机便携 工业控制 家用电子 医疗电子 测试测量 网络通信 物联网

北京市海淀区中关村大街18号B座15层1530室 电话:(010)82350740 邮编:100190

电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2025 EEWORLD.com.cn, Inc. All rights reserved
快速回复 返回顶部 返回列表