第19章 码类识别
第十九章 码类识别
1. 一维码识别
生活中最常见的一维码就是你购买各种商品上的条形码了,它是一种通过不同宽度的黑白条纹来表示信息的图形标识符。条形码通常是由不同宽度的黑条和白条交替排列(黑色吸收光中的所有颜色,而白色反射光中的所有颜色,对比度很高,最常用),并按照特定的编码规则设计。商品上的条形码能够携带商品的各种信息,当然我们也可以直接将字节信息直接编入一维码中。
除了特定的商用场景会用到激光扫描器,在日常生活中我们最常用的就是用手机来扫描一维码,手机通过摄像头拍摄条形码,并将其图像输入到条码识别软件中。软件首先会对图像进行处理,识别出其中的黑条和白条。由于黑条吸光而白条反射光,条形码的扫描器(在手机中由摄像头和软件共同完成)通过光电转换将光信号转化为电信号。这些电信号的强度差异反映了条形码中每个黑条和白条的不同宽度。
1.1 image.find_barcodes(roi)
该函数查找指定 ROI 内的所有一维条形码,并返回一个包含 image.barcode
对象的列表。有关更多信息,请参考 image.barcode
对象的相关文档。
为了获得最佳效果,建议使用长为 640 像素、宽为 40/80/160 像素的窗口。窗口的垂直程度越低,运行速度越快。由于条形码是线性一维图像,因此在一个方向上需具有较高分辨率,而在另一个方向上可具有较低分辨率。请注意,该函数会进行水平和垂直扫描,因此您可以使用宽为 40/80/160 像素、长为 480 像素的窗口。请务必调整镜头,使条形码位于焦距最清晰的区域。模糊的条形码无法解码。
该函数支持以下所有一维条形码:
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
roi
是用于指定感兴趣区域的矩形元组(x, y, w, h)
。若未指定,ROI 默认为整个图像。操作范围仅限于该区域内的像素。
1.2 示例代码
import time, math, os, gc, sys
from media.sensor import *
from media.display import *
from media.media import *
sensor_id = 2
sensor = None
# 处理尺寸
picture_width = 640
picture_height = 480
DISPLAY_MODE = "LCD"
# 根据模式设置显示宽高
if DISPLAY_MODE == "VIRT":
# 虚拟显示器模式
DISPLAY_WIDTH = ALIGN_UP(1920, 16)
DISPLAY_HEIGHT = 1080
elif DISPLAY_MODE == "LCD":
# 3.1寸屏幕模式
DISPLAY_WIDTH = 800
DISPLAY_HEIGHT = 480
elif DISPLAY_MODE == "HDMI":
# HDMI扩展板模式
DISPLAY_WIDTH = 1920
DISPLAY_HEIGHT = 1080
else:
raise ValueError("未知的 DISPLAY_MODE,请选择 'VIRT', 'LCD' 或 'HDMI'")
# 一维码识别
def barcode_reader(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(id=sensor_id)
sensor.reset()
sensor.set_framesize(width=picture_width, height=picture_height, chn=CAM_CHN_ID_0)
sensor.set_pixformat(Sensor.GRAYSCALE, chn=CAM_CHN_ID_0)
# sensor.set_pixformat(Sensor.RGB565, chn=CAM_CHN_ID_0)
# 根据模式初始化显示器
if DISPLAY_MODE == "VIRT":
Display.init(Display.VIRT, width=DISPLAY_WIDTH, height=DISPLAY_HEIGHT, fps=60)
elif DISPLAY_MODE == "LCD":
Display.init(Display.ST7701, width=DISPLAY_WIDTH, height=DISPLAY_HEIGHT, to_ide=True)
elif DISPLAY_MODE == "HDMI":
Display.init(Display.LT9611, width=DISPLAY_WIDTH, height=DISPLAY_HEIGHT, to_ide=True)
MediaManager.init()
sensor.run()
fps = time.clock()
while True:
os.exitpoint()
fps.tick()
# 捕获通道0的图像
img = sensor.snapshot(chn=CAM_CHN_ID_0)
# 寻找条形码
for code in img.find_barcodes():
img.draw_rectangle([v for v in code.rect()], color=(255, 0, 0), thickness=5)
# 打印条形码信息
print_args = (f"条形码: {barcode_reader(code)}", code.payload(), (180*code.payload()/math.pi, code.quality(), fps.fps()))
print("Barcode %s, Payload \"%s\", rotation %f (degrees), quality %d, FPS %f" % print_args)
# 显示捕获到的图像
Display.show_image(img, x=int((DISPLAY_WIDTH - picture_width) / 2), y=int((DISPLAY_HEIGHT - picture_height) / 2))
except KeyboardInterrupt as e:
print("用户停止: ", e)
except BaseException as e:
print(f"异常: {e}")
finally:
if isinstance(sensor, Sensor):
sensor.stop()
# 反初始化显示模块
Display.deinit()
os.exitpoint(os.EXITPOINT_ENABLE_SLEEP)
time.sleep_ms(100)
MediaManager.deinit()
2. 识别二维码
二维码的出现是为了解决传统一维条形码信息量有限的问题,二维码具有存储更多信息的优势。它的应用范围从商品追踪到支付系统,尤其在移动支付中得到了广泛的应用。现在中国如此发达的交易支付体系离不开二维码的普及,尤其是在扫码支付上,二维码几乎成为了每个商户的标配。二维码通过黑白模块组成矩阵,可以存储更大量的信息。它的优势在于能够容纳的字符远多于传统的一维条形码,且识别速度快,扫描精度高,极大地方便了日常生活中的支付、社交、信息交换等。
二维码由多个黑白方块组成,每个模块代表一个数据单元,排列成一个正方形的网格结构。二维码不仅可以存储数字信息,还可以存储字母、汉字甚至网址等信息。二维码的解析不仅要识别出二维码的位置和方向,还需要解码其中包含的数据。所以在识别二维码时就需要首先对图像处理(本章不涉及,直接使用一条函数解决处理和识别),比如边缘检测,二值化等;然后要定位二维码的位置,一般的二维码都有三个定位位置;最后就是解码了。
2.1 image.find_qrcodes(roi)
image.find_qrcodes([roi])
该函数查找指定 ROI 内的所有二维码,并返回一个包含 image.qrcode
对象的列表。有关更多信息,请参考 image.qrcode
对象的相关文档。
为了确保该方法成功运行,图像上的二维码需尽量平展。可以通过使用 sensor.set_windowing
函数在镜头中心放大、使用 image.lens_corr
函数消除镜头的桶形畸变,或更换视野较窄的镜头,获得不受镜头畸变影响的平展二维码。部分机器视觉镜头不产生桶形失真,但其成本高于 OpenMV 提供的标准镜头,这些镜头为无畸变镜头。
roi
是用于指定感兴趣区域的矩形元组(x, y, w, h)
。若未指定,ROI 默认为整个图像。操作范围仅限于该区域内的像素。
2.2 示例代码
from media.sensor import *
from media.display import *
from media.media import *
import time, math, os, gc, sys
picture_width = 800
picture_height = 480
sensor_id = 2
sensor = None
# 显示模式选择:可以是 "VIRT"、"LCD" 或 "HDMI"
DISPLAY_MODE = "LCD"
# 根据模式设置显示宽高
if DISPLAY_MODE == "VIRT":
# 虚拟显示器模式
DISPLAY_WIDTH = ALIGN_UP(1920, 16)
DISPLAY_HEIGHT = 1080
elif DISPLAY_MODE == "LCD":
# 3.1寸屏幕模式
DISPLAY_WIDTH = 800
DISPLAY_HEIGHT = 480
elif DISPLAY_MODE == "HDMI":
# HDMI扩展板模式
DISPLAY_WIDTH = 1920
DISPLAY_HEIGHT = 1080
else:
raise ValueError("未知的 DISPLAY_MODE,请选择 'VIRT', 'LCD' 或 'HDMI'")
# 自定义函数,用于返回条形码类型名称
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(id=sensor_id)
sensor.reset()
# 设置通道0的输出尺寸
sensor.set_framesize(width=picture_width, height=picture_height, chn=CAM_CHN_ID_0)
# 设置通道0的输出像素格式为GRAYSCALE(灰度)
sensor.set_pixformat(Sensor.GRAYSCALE, chn=CAM_CHN_ID_0)
# sensor.set_pixformat(Sensor.RGB565, chn=CAM_CHN_ID_0)
# 根据模式初始化显示器
if DISPLAY_MODE == "VIRT":
Display.init(Display.VIRT, width=DISPLAY_WIDTH, height=DISPLAY_HEIGHT, fps=60)
elif DISPLAY_MODE == "LCD":
Display.init(Display.ST7701, width=DISPLAY_WIDTH, height=DISPLAY_HEIGHT, to_ide=True)
elif DISPLAY_MODE == "HDMI":
Display.init(Display.LT9611, width=DISPLAY_WIDTH, height=DISPLAY_HEIGHT, to_ide=True)
MediaManager.init()
sensor.run()
fps = time.clock()
while True:
os.exitpoint()
fps.tick()
# 捕获通道0的图像
img = sensor.snapshot(chn=CAM_CHN_ID_0)
# 遍历捕获到的所有条形码
for code in img.find_qrcodes():
# 在图像上绘制条形码矩形框
rect = code.rect()
img.draw_rectangle([v for v in rect], color=(255, 0, 0), thickness=5)
# 打印条形码信息
img.draw_string_advanced(rect[0], rect[1], 32, code.payload())
print(code)
# 显示捕获的图像,中心对齐,居中显示
Display.show_image(img, x=int((DISPLAY_WIDTH - picture_width) / 2),
y=int((DISPLAY_HEIGHT - picture_height) / 2))
except KeyboardInterrupt as e:
print("用户停止: ", e)
except BaseException as e:
print(f"异常: {e}")
finally:
# 停止传感器运行
if isinstance(sensor, Sensor):
sensor.stop()
# 反初始化显示模块
Display.deinit()
os.exitpoint(os.EXITPOINT_ENABLE_SLEEP)
time.sleep_ms(100)
# 释放媒体缓冲区
MediaManager.deinit()
3. 机器码识别(AprilTag)
AprilTag是一种基于二维码的视觉标记系统,最早是由麻省理工学院(MIT)在2008年开发的。AprilTag的设计目标是为机器人视觉系统提供快速、可靠的视觉标记,特别适用于定位和跟踪应用。相比二维码和一维码,AprilTag有着更高的精度和更强的鲁棒性。
AprilTag通过在正方形的图案中使用独特的黑白图案来进行信息编码。与二维码不同,AprilTag的特点是它通过不同的标记ID来标识不同的标签,本身还包括误差修正能力,能够在一定程度上适应环境噪声和图像变形。一个AprilTag一般由一个黑色的边框和一组内部的二进制编码区域组成。每个AprilTag的编码方式是不同的,也就是说每个标记都有唯一的ID。
3.1 image.find_apriltags()
image.find_apriltags([roi[, families=image.TAG36H11[, fx[, fy[, cx[, cy]]]]]])
该函数查找指定 ROI 内的所有 AprilTag,并返回一个包含 image.apriltag
对象的列表。有关更多信息,请参考 image.apriltag
对象的相关文档。
与二维码相比,AprilTags 可以在更远的距离、较差的光照条件和更扭曲的图像环境下被有效检测。AprilTags 能够应对各种图像失真问题,而二维码则不能。因此,AprilTags 仅将数字 ID 编码作为其有效载荷。
此外,AprilTags 还可用于定位。每个 image.apriltag
对象将返回其三维位置信息和旋转角度。位置信息由 fx
、fy
、cx
和 cy
决定,分别表示图像在 X 和 Y 方向上的焦距和中心点。
可以使用 OpenMV IDE 内置的标签生成器工具创建 AprilTags。该工具可生成可打印的 8.5”x11” 格式的 AprilTags。
roi
是用于指定感兴趣区域的矩形元组(x, y, w, h)
。若未指定,ROI 默认为整个图像。操作范围仅限于该区域内的像素。families
是要解码的标签家族的位掩码,以逻辑或形式表示:image.TAG16H5
image.TAG25H7
image.TAG25H9
image.TAG36H10
image.TAG36H11
image.ARTOOLKIT
默认设置为最常用的 image.TAG36H11
标签家族。请注意,启用每个标签家族都会稍微降低 find_apriltags
的速度。
fx
是以像素为单位的相机 X 方向的焦距。标准 OpenMV Cam 的值为 ((2.8 / 3.984) \times 656),该值通过毫米计的焦距值除以 X 方向上感光元件的长度,再乘以 X 方向上感光元件的像素数量(针对 OV7725 感光元件)。fy
是以像素为单位的相机 Y 方向的焦距。标准 OpenMV Cam 的值为 ((2.8 / 2.952) \times 488),该值通过毫米计的焦距值除以 Y 方向上感光元件的长度,再乘以 Y 方向上感光元件的像素数量(针对 OV7725 感光元件)。cx
是图像的中心,即image.width()/2
,而非roi.w()/2
。cy
是图像的中心,即image.height()/2
,而非roi.h()/2
。
3.2 示例代码
from media.sensor import *
from media.display import *
from media.media import *
import time, math, os, gc, sys
picture_width = 800
picture_height = 480
sensor_id = 2
sensor = None
# 显示模式选择:可以是 "VIRT"、"LCD" 或 "HDMI"
DISPLAY_MODE = "LCD"
# 根据模式设置显示宽高
if DISPLAY_MODE == "VIRT":
# 虚拟显示器模式
DISPLAY_WIDTH = ALIGN_UP(1920, 16)
DISPLAY_HEIGHT = 1080
elif DISPLAY_MODE == "LCD":
# 3.1寸屏幕模式
DISPLAY_WIDTH = 800
DISPLAY_HEIGHT = 480
elif DISPLAY_MODE == "HDMI":
# HDMI扩展板模式
DISPLAY_WIDTH = 1920
DISPLAY_HEIGHT = 1080
else:
raise ValueError("未知的 DISPLAY_MODE,请选择 'VIRT', 'LCD' 或 'HDMI'")
# 注意!与find_qrcodes不同,find_apriltags方法无需对图像进行镜像校正。
# apriltag代码支持最多6个标签族,可以同时处理多个标签。
# 返回的标签对象将包含标签族和标签族内的标签ID。
tag_families = 0
#tag_families |= image.TAG16H5 # comment out to disable this family
#tag_families |= image.TAG25H7 # comment out to disable this family
#tag_families |= image.TAG25H9 # comment out to disable this family
#tag_families |= image.TAG36H10 # comment out to disable this family
tag_families |= image.TAG36H11 # comment out to disable this family (default family)
#tag_families |= image.ARTOOLKIT # comment out to disable this family
# 标签族之间有什么区别?例如,TAG16H5标签族是一个4x4的正方形标签,
# 这意味着它可以在较远的距离检测到,而TAG36H11标签族是6x6的正方形标签。
# 然而,较低的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(id=sensor_id)
sensor.reset()
# 设置通道0的输出尺寸
sensor.set_framesize(width=picture_width, height=picture_height, chn=CAM_CHN_ID_0)
# 设置通道0的输出像素格式为GRAYSCALE(灰度)
sensor.set_pixformat(Sensor.GRAYSCALE, chn=CAM_CHN_ID_0)
#sensor.set_pixformat(Sensor.RGB565, chn=CAM_CHN_ID_0)
# 根据模式初始化显示器
if DISPLAY_MODE == "VIRT":
Display.init(Display.VIRT, width=DISPLAY_WIDTH, height=DISPLAY_HEIGHT, fps=60)
elif DISPLAY_MODE == "LCD":
Display.init(Display.ST7701, width=DISPLAY_WIDTH, height=DISPLAY_HEIGHT, to_ide=True)
elif DISPLAY_MODE == "HDMI":
Display.init(Display.LT9611, width=DISPLAY_WIDTH, height=DISPLAY_HEIGHT, to_ide=True)
MediaManager.init()
sensor.run()
fps = time.clock()
while True:
os.exitpoint()
fps.tick()
img = sensor.snapshot(chn=CAM_CHN_ID_0)
# 查找并处理AprilTag标签
for tag in img.find_apriltags(families=tag_families):
# 在图像中绘制标签的矩形框
img.draw_rectangle([v for v in tag.rect()], color=(255, 0, 0))
# 在标签中心绘制十字
img.draw_cross(tag.cx(), tag.cy(), color=(0, 255, 0))
# 显示标签ID
img.draw_string_advanced(tag.cx(), tag.cy(), 32, str(tag.id()))
print_args = (family_name(tag), tag.id(), (180 * tag.rotation()) / math.pi)
print("Tag Family %s, Tag ID %d, rotation %f (degrees)" % print_args)
# 显示捕获的图像,中心对齐,居中显示
Display.show_image(img, x=int((DISPLAY_WIDTH - picture_width) / 2), y=int((DISPLAY_HEIGHT - picture_height) / 2))
gc.collect()
print(fps.fps())
except KeyboardInterrupt as e:
print("用户停止: ", e)
except BaseException as e:
print(f"异常: {e}")
finally:
# 停止传感器运行
if isinstance(sensor, Sensor):
sensor.stop()
# 反初始化显示模块
Display.deinit()
os.exitpoint(os.EXITPOINT_ENABLE_SLEEP)
time.sleep_ms(100)
# 释放媒体缓冲区
MediaManager.deinit()
本文作者:hazy1k
本文链接:https://www.cnblogs.com/hazy1k/p/18730670
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步