人若无名 便可潜心练剑.|

hazy1k

园龄:7个月粉丝:13关注:0

2025-02-22 10:54阅读: 6评论: 0推荐: 0

第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 对象将返回其三维位置信息和旋转角度。位置信息由 fxfycx 和 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 中国大陆许可协议进行许可。

posted @   hazy1k  阅读(6)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起