嘉楠K230AI开发板测评6--条形码、二维码与AprilTag标签识别
<p align="center"><b>嘉楠科K230AI开发板</b><b>测评6--机器视觉篇</b></p><p><strong>1、条形码识别</strong></p>
<p> 条形码(barcode)是将宽度不等的多个黑条和空白,按照一定的编码规则排列,用以表达一组信息的图形标识符。常见的条形码是由反射率相差很大的黑条(简称条)和白条(简称空)排成的平行线图案。条形码可以标出物品的生产国、制造厂家、商品名称、生产日期、图书分类号、邮件起止地点、类别、日期等许多信息,因而在商品流通、图书管理、邮政管理、银行系统等许多领域都得到广泛的应用。</p>
<p > 编程实现条形码识别,并将识别到的信息通过串口终端打印出来。</p>
<p > 对于CanMV K230而言,直接使用MicroPython中的find_barcodes()即可获取摄像头采集图像中条形码的相关信息。</p>
<p > 该函数支持所有一维条形码:image.EAN2 image.EAN5 image.EAN8 image.UPCE image.ISBN10 image.UPCA image.EAN13 image.ISBN13 image.I25 image.DATABAR (RSS-14) image.DATABAR_EXP (RSS-Expanded) image.CODABAR image.CODE39 image.PDF417 image.CODE93 image.CODE128</p>
<p >条形码对象是由 image.find_barcodes 返回的。</p>
<p > barcode.corners()返回一个由该对象的四个角组成的四个元组(x,y)的列表。四个角通常是按照从左上角开始沿顺时针顺序返回的。</p>
<p > barcode.rect()返回一个矩形元组(x, y, w, h),用于如数据矩阵的边界框的 image.draw_rectangle 等其他的 image 方法。</p>
<p > barcode.payload()返回条形码的有效载荷的字符串。例:数量。</p>
<p > barcode.type()返回条形码的列举类型 (int)。</p>
<p > barcode.rotation()返回以弧度计的条形码的旋度(浮点数)。</p>
<p > barcode.quality()返回条形码在图像中被检测到的次数(int)。</p>
<p > 调用find_barcodes()函数,对得到的结果再进行处理即可,代码编写流程如下:</p>
<p style="text-align: center;"> </p>
<p align="justify"> 参考代码如下:</p>
<pre>
<code class="language-python">'''
实验名称:条形码识别
实验平台:01Studio CanMV K230
说明:编程实现摄像头识别各类条形码
'''
import time, math, os, gc
from media.sensor import * #导入sensor模块,使用摄像头相关接口
from media.display import * #导入display模块,使用display相关接口
from media.media import * #导入media模块,使用meida相关接口
#定义条形码类型
def barcode_name(code):
if(code.type() == image.EAN2):
return "EAN2"
if(code.type() == image.EAN5):
return "EAN5"
if(code.type() == image.EAN8):
return "EAN8"
if(code.type() == image.UPCE):
return "UPCE"
if(code.type() == image.ISBN10):
return "ISBN10"
if(code.type() == image.UPCA):
return "UPCA"
if(code.type() == image.EAN13):
return "EAN13"
if(code.type() == image.ISBN13):
return "ISBN13"
if(code.type() == image.I25):
return "I25"
if(code.type() == image.DATABAR):
return "DATABAR"
if(code.type() == image.DATABAR_EXP):
return "DATABAR_EXP"
if(code.type() == image.CODABAR):
return "CODABAR"
if(code.type() == image.CODE39):
return "CODE39"
if(code.type() == image.PDF417):
return "PDF417"
if(code.type() == image.CODE93):
return "CODE93"
if(code.type() == image.CODE128):
return "CODE128"
try:
sensor = Sensor() #构建摄像头对象
sensor.reset() #复位和初始化摄像头
#sensor.set_framesize(Sensor.FHD) #设置帧大小FHD(1920x1080),默认通道0
sensor.set_framesize(width=800, height=480) #设置帧大小VGA,默认通道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() #拍摄图片
codes = img.find_barcodes() #查找图像中所有条形码
for code in codes:
#对条码画矩形表示
img.draw_rectangle(code.rect(),thickness=2)
#打印相关信息
print_args = (barcode_name(code), code.payload(), (180 * code.rotation()) / math.pi, code.quality())
print("Barcode %s, Payload \"%s\", rotation %f (degrees), quality %d" % print_args)
img.draw_string_advanced(0, 0, 30, code.payload(), color = (255, 255, 255)) #图像显示条码信息
Display.show_image(img) #显示图片
print(clock.fps()) #打印帧率
###################
# IDE中断释放资源代码
###################
except KeyboardInterrupt as e:
print(f"user stop")
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 align="justify"> 实验结果如下,为了更好地识别,图像上条形码需比较平展,不能太小;在线生成一个条形码值位“ABC-abc-1234”的Code 128型条形码,运行程序,打开条形码图片。摄像头正对条形码,识别成功后可以看到图片出现方框以及在串口终端打印出条形码信息。</p>
<p align="justify" style="text-align: center;"> </p>
<p><strong>2、二维码识别</strong></p>
<p> 二维码又称二维条码,常见的二维码为QR Code,QR全称Quick Response,是一个近几年来移动设备上超流行的一种编码方式,它比传统的Bar Code条形码能存更多的信息,也能表示更多的数据类型。</p>
<p > 二维条码/二维码(2-dimensional bar code)是用某种特定的几何图形按一定规律在平面(二维方向上)分布的、黑白相间的、记录数据符号信息的图形;在代码编制上巧妙地利用构成计算机内部逻辑基础的“0”、“1”比特流的概念,使用若干个与二进制相对应的几何形体来表示文字数值信息,通过图象输入设备或光电扫描设备自动识读以实现信息自动处理:它具有条码技术的一些共性:每种码制有其特定的字符集;每个字符占有一定的宽度;具有一定的校验功能等。同时还具有对不同行的信息自动识别功能、及处理图形旋转变化点。</p>
<p > MicroPython中的find_qrcodes()即可获取摄像头采集图像中二维码的相关信息。</p>
<p > 二维码对象是由 image.find_qrcodes 返回的。</p>
<p > qrcode.corners()返回一个由该对象的四个角组成的四个元组(x,y)的列表。四个角通常是按照从左上角开始沿顺时针顺序返回的。</p>
<p > qrcode.rect()返回一个矩形元组(x, y, w, h),用于如二维码的边界框的 image.draw_rectangle 等其他的 image 方法。</p>
<p > qrcode.payload()返回二维码有效载荷的字符串,例如URL 。</p>
<p > qrcode.version()返回二维码的版本号(int)。</p>
<p > qrcode.ecc_level()返回二维码的ECC水平(int)。</p>
<p > qrcode.mask()返回二维码的掩码(int)。</p>
<p > qrcode.data_type()返回二维码的数据类型。</p>
<p > qrcode.eci()返回二维码的ECI。ECI储存了QR码中存储数据字节的编码。若想要处理包含超过标准ASCII文本的二维码,您需要查看这一数值。</p>
<p > MicroPython编程我们只需要简单地调用find_qrcodes()函数,对得到的结果再进行处理即可,代码编写流程如下:</p>
<p style="text-align: center;"> </p>
<p> 核心代码如下:</p>
<pre>
<code class="language-python">''''''''''''
img = sensor.snapshot() #拍摄图片
res = img.find_qrcodes() #寻找所有二维码,返回列表,每个值为一个二维码
if len(res) > 0: #在图片和终端显示二维码信息
img.draw_rectangle(res.rect(), thickness=2)#列表不能属性,列表的值可以属性
img.draw_string_advanced(0, 0, 30, res.payload(), color = (255, 255, 255))
print(res.payload()) #串口终端打印
Display.show_image(img) #显示图片
print(clock.fps()) #打印帧率
''''''''''''</code></pre>
<p> 实验结果,为了更好地识别,图像上二维码需比较平展,不能太小,在线生成一个值为“<a href="https://www.bing.com" target="_blank">https://www.bing.com</a>”的QR码,运行程序,打开二维码图片;摄像头正对二维码,识别成功后可以看到图片出现方框以及在串口终端打印出二维码信息。</p>
<p style="text-align: center;"> </p>
<p><strong>3、AprilTag标签识别</strong></p>
<p><b> AprilTag</b>是一种视觉基准系统,可用于多种任务,包括增强现实、机器人和相机校准。可以通过普通打印机创建目标,AprilTag 检测软件可以计算标签相对于相机的精确3D位置、方向和标识。</p>
<p ><b> AprilTag</b>通过特定的标志(与二维码相似,但是降低了复杂度以满足实时性要求),可以快速地检测标志,并计算相对位置。</p>
<p ><b> AprilTag</b>内容主要包含三个步骤:</p>
<p > 第一步是如何根据梯度检测出图像中的各种边缘。</p>
<p > 第二步即如何在边缘图像中找出需要的四边形图案并进行筛选,AprilTag尽可能的对检测出的边缘检测,首先剔除非直线边缘,在直线边缘进行邻接边缘查找,最终若形成闭环则为检测到一个四边形。</p>
<p > 最后一个步便是如何进行二维码编码和二维码解码,编码方式分为三种,其黑边色块长度分别为8,7,6三个色块长度,对于解码内容,要在检测到的四边形内生成点阵列用于计算每色块的值,再根据局部二值模式(Local Binary Patterns)构造简单分类器对四边形内的色块进行分类,将正例色块编码为1将负例色块编码为0,就可以得到该二维码的编码。得到编码以后再与已知库内的编码进行匹配,确定解码出的二维码是否为正确。</p>
<p >可以将AprilTag简单地理解为一个特定信息的<b>二维码</b>,有family和ID两个概念:</p>
<p > TAG16H5 → 0 to 29</p>
<p > TAG25H7 → 0 to 241</p>
<p > TAG25H9 → 0 to 34</p>
<p > TAG36H10 → 0 to 2319</p>
<p > TAG36H11 → 0 to 586 (CanMV K230推荐使用)</p>
<p > ARTOOLKIT → 0 to 511</p>
<p > 以【TAG36H11 → 0 to 586】为例,family信息就是:TAG36H11 , ID可以是“0 到 586” ,也就是一共有587种标记码。</p>
<p ><b> 不同家族区别:</b>TAG16H5的有效区域是 4x4 的方块,那么它比TAG36H11看的更远(因为他有 6x6 个方块)。 但是内容少,所以TAG16H5的错误率比TAG36H11 高很多,因为TAG36H11的校验信息多。CanMV K210推荐使用TAG36H11家族的标记码。</p>
<p > 可以在CanMV IDE生成AprilTag。点击<b>工具--机器视觉--AprilTag生成器--TAG36H11家族:</b>最小输入0 ,最大输入9 ,制作id从0-9共10张标签。点击OK后选择要生成的位置文件夹即可,如下图:</p>
<p style="text-align: center;"> </p>
<p> 识别apriltag使用find_apriltags对象函数,返回一个 image.apriltag 对象的列表。</p>
<p > 与二维码相比,AprilTags可在更远距离、较差光线和更扭曲的图像环境下被检测到。 AprilTags可应对所有种类的图像失真问题,而二维码并不能。也就是说,AprilTags只能将数字ID编码作为其有效载荷。</p>
<p > AprilTags也可用于本地化。每个 image.apriltag 对象都从摄像机返回其三维位置信息和旋转角度。 位置信息由 fx 、 fy 、 cx 和 cy 决定,分别为X和Y方向上图像的焦距和中心点。</p>
<p > tag.rect()返回一个矩形元组(x,y,w,h),二维码的边界。可以通过索引来获得单个值。</p>
<p > tag.family()家族信息。</p>
<p > tag.id()ID信息。</p>
<p > tag.rotation()方向。</p>
<p > 代码编写流程如下:</p>
<p style="text-align: center;"> </p>
<p> 参考代码如下:</p>
<pre>
<code class="language-python">'''
实验名称:AprilTags标签识别
实验平台:01Studio CanMV K230
教程:wiki.01studio.cc
说明:推荐使用QVGA(320x240)分辨率,分辨率太高帧率会下降。
'''
import time, math, os, gc
from media.sensor import * #导入sensor模块,使用摄像头相关接口
from media.display import * #导入display模块,使用display相关接口
from media.media import * #导入media模块,使用meida相关接口
# apriltag代码最多支持可以同时处理6种tag家族。
# 返回的tag标记对象,将有其tag标记家族及其在tag标记家族内的id。
tag_families = 0
tag_families |= image.TAG16H5 # 注释掉,禁用这个家族
tag_families |= image.TAG25H7 # 注释掉,禁用这个家族
tag_families |= image.TAG25H9 # 注释掉,禁用这个家族
tag_families |= image.TAG36H10 # 注释掉,禁用这个家族
tag_families |= image.TAG36H11 # 注释掉以禁用这个家族(默认家族)
tag_families |= image.ARTOOLKIT # 注释掉,禁用这个家族
#标签系列有什么区别? 那么,例如,TAG16H5家族实际上是一个4x4的方形标签。
#所以,这意味着可以看到比6x6的TAG36H11标签更长的距离。
#然而,较低的H值(H5对H11),意味着4x4标签的假阳性率远高于6x6标签。
#所以,除非你有理由使用其他标签系列,否则使用默认族TAG36H11。
def family_name(tag):
if(tag.family() == image.TAG16H5):
return "TAG16H5"
if(tag.family() == image.TAG25H7):
return "TAG25H7"
if(tag.family() == image.TAG25H9):
return "TAG25H9"
if(tag.family() == image.TAG36H10):
return "TAG36H10"
if(tag.family() == image.TAG36H11):
return "TAG36H11"
if(tag.family() == image.ARTOOLKIT):
return "ARTOOLKIT"
try:
sensor = Sensor(width=1280, height=960) #构建摄像头对象,将摄像头长宽设置为4:3
sensor.reset() #复位和初始化摄像头
sensor.set_framesize(width=320, height=240) #设置帧大小为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() #拍摄图片
for tag in img.find_apriltags(families=tag_families): # 如果没有给出家族,默认TAG36H11。
img.draw_rectangle(tag.rect(), color = (255, 0, 0), thickness=4)
img.draw_cross(tag.cx(), tag.cy(), color = (0, 255, 0), thickness=2)
print_args = (family_name(tag), tag.id(), (180 * tag.rotation()) / math.pi) #打印标签信息
print("Tag Family %s, Tag ID %d, rotation %f (degrees)" % print_args)
#img.draw_string_advanced(0, 0, 30, code.payload(), 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()) #打印帧率
###################
# IDE中断释放资源代码
###################
except KeyboardInterrupt as e:
print(f"user stop")
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> 实验结果如下,打开family: TAG36H11 , id: 0的标签图片测试:</p>
<p style="text-align: center;"> </p>
页:
[1]