第17章 特征检测
第七章 特征检测
1. 线段检测
线段检测的基本思想有两个:
- 空间变换:霍夫变换中,会将图像边缘像素映射到一个极坐标或其它累加空间,并在累加空间中找极大值点对应的直线或线段。
- 后续合并:如果检出很多近似平行并且重叠度较高的线段,会做一个“合并距离”与“角度差”判断,把它们合并成一条线段。在庐山派中就是体现为两个参数,
merge_distance
和max_theta_difference
。
1.1 find_line_segments
image.find_line_segments(roi, merge_distance=0, max_theta_difference=15)
该函数使用霍夫变换查找图像中的线段,并返回一个 image.line
对象的列表。
roi
为感兴趣区域的矩形元组(x, y, w, h)
。若未指定,ROI 默认为整个图像的矩形。操作仅限于该区域内的像素。merge_distance
指定两条线段之间的最大像素距离,若小于该值则合并为一条线段。max_theta_difference
为需合并的两条线段之间的最大角度差。
该方法使用 LSD 库(OpenCV 亦采用)来查找图像中的线段。虽然速度较慢,但准确性高,且线段不会出现跳跃现象。
1.2 使用示例
import time, os, sys
from media.sensor import *
from media.display import *
from media.media import *
sensor_id = 2
sensor = None
# 处理尺寸
picture_width = 400
picture_height = 240
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'")
try:
sensor = Sensor(id=sensor_id,width=1920, height=1080)
sensor.reset()
sensor.set_framesize(width=picture_width, height=picture_height, 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()
while True:
os.exitpoint()
# 捕获通道0的图像
img = sensor.snapshot(chn=CAM_CHN_ID_0)
# 查找线段(LSD算法)
lines = img.find_line_segments(merge_distance=5, max_theta_diff=10)
count = 0
print("----------线段统计开始----------")
for line in lines:
img.draw_line(line.line(), color=(1, 147, 230), thickness=3) # 画线
print(f"Line {count}: {line}")
count += 1
print("----------线段统计结束----------")
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. 矩形检测
矩形检测一般用于条码检测,可以先定位条码或者二维码,然后再去解码,提高运行速度。也常用来OCR的前置检测,先定位文档中的文字块或段落。CanMV固件中的find_rects
函数使用了与 AprilTag 中的四边形检测类似的算法,能适应一定程度的平移、旋转和仿射变化(扭曲)。
2.1 find_rects
image.find_rects([roi=Auto, threshold=10000])
此函数使用与 AprilTag 相同的四边形检测算法查找图像中的矩形。该算法最适用于与背景形成鲜明对比的矩形。AprilTag 的四边形检测能够处理任意缩放、旋转和剪切的矩形,并返回一个包含 image.rect
对象的列表。
roi
是用于指定感兴趣区域的矩形元组(x, y, w, h)
。若未指定,ROI 默认为整个图像。操作范围仅限于该区域内的像素。
在返回的矩形列表中,边界大小(通过在矩形边缘的所有像素上滑动索贝尔算子并累加其值)小于 threshold
的矩形将被过滤。适当的 threshold
值取决于具体的应用场景。
注意: 不支持压缩图像和 Bayer 图像。
2.2 使用示例
import time, os, sys
from media.sensor import *
from media.display import *
from media.media import *
sensor_id = 2
sensor = None
# 处理尺寸
picture_width = 400
picture_height = 240
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'")
try:
sensor = Sensor(id=sensor_id,width=1920, height=1080)
sensor.reset()
sensor.set_framesize(width=picture_width, height=picture_height, 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()
while True:
os.exitpoint()
# 捕获通道0的图像
img = sensor.snapshot(chn=CAM_CHN_ID_0)
# 查找矩形
rects = img.find_rects(threshold=5000)
count = 0
print("----------统计开始----------")
for rect in rects:
img.draw_rectangle(rect.rect(), color=(1, 147, 230), thickness=3) # 画矩形
print(f"Rect {count}: {rect}")
count += 1
print("----------统计结束----------")
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.圆形检测
可以用来快速定位圆心与半径,可以用来检测圆环标记,交通标志等,也可以用于物体检测(如五子棋棋子定位)。
3.1 find_circles
image.find_circles([roi[, x_stride=2[, y_stride=1[, threshold=2000[, x_margin=10[, y_margin=10[, r_margin=10]]]]]]])
该函数使用霍夫变换在图像中查找圆形,并返回一个 image.circle
对象的列表。
roi
为感兴趣区域的矩形元组(x, y, w, h)
。若未指定,ROI 默认为整个图像的矩形。操作仅限于该区域内的像素。x_stride
为霍夫变换过程中需要跳过的 x 像素数量。如果已知圆较大,可增加x_stride
。y_stride
为霍夫变换过程中需要跳过的 y 像素数量。如果已知圆较大,可增加y_stride
。threshold
控制检测到的圆的大小,仅返回大于或等于该阈值的圆。合适的阈值取决于图像内容。请注意,圆的大小(magnitude)是构成圆的所有索贝尔滤波像素
大小的总和。
x_margin
为对 x 坐标进行合并时允许的最大像素偏差。y_margin
为对 y 坐标进行合并时允许的最大像素偏差。r_margin
为对半径进行合并时允许的最大像素偏差。
该方法通过在图像上应用索贝尔滤波器,并利用其幅值和梯度响应执行霍夫变换。无需对图像进行任何预处理,尽管图像的清理和过滤将会产生更为稳定的结果。
注意: 此功能不支持压缩图像和 Bayer 图像。
3.2 使用示例
import time, os, sys
from media.sensor import *
from media.display import *
from media.media import *
sensor_id = 2
sensor = None
# 处理尺寸
picture_width = 400
picture_height = 240
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'")
try:
sensor = Sensor(id=sensor_id,width=1920, height=1080)
sensor.reset()
sensor.set_framesize(width=picture_width, height=picture_height, 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()
while True:
os.exitpoint()
# 捕获通道0的图像
img = sensor.snapshot(chn=CAM_CHN_ID_0)
# 查找圆形
circles = img.find_circles(threshold=6000)
count = 0
print("----------统计开始----------")
for circle in circles:
img.draw_circle(circle.circle(), color=(1, 147, 230), thickness=3) # 画矩形
print(f"Circle {count}: {circle}")
count += 1
print("----------统计结束----------")
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()
本文作者:hazy1k
本文链接:https://www.cnblogs.com/hazy1k/p/18725885
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步