本节例程代码位于:【源码汇总 / 04.Detecting / 01.find_lines】
我们用CanMV IDE打开例程代码,将K230用USB连接到电脑上
点击CanMV IDE左下角的运行按钮,
将K230的摄像头对准线段
可以看到屏幕上会标记出画面中的线段(如果没有屏幕就看帧缓冲区)
原图:

K230识别结果:

本节我们要用的的外设主要是摄像头模块
线段检测由 K230中的 find_line_segments() 方法实现,该方法属于image模块
例程使用的代码如下:
*以【源码汇总 / 04.Detecting / 01.find_lines.py】文件内容为准
x# 导入必要的模块:时间、操作系统、系统、垃圾回收# (Import required modules: time, operating system, system, garbage collection)import time, os, sys, gc# 导入媒体相关模块:传感器、显示、媒体管理# (Import media-related modules: sensor, display, media management)from media.sensor import *from media.display import *from media.media import *# 导入PipeLine库,用于图像处理Pipeline和性能计时# (Import PipeLine library for image processing pipeline and performance timing)from libs.PipeLine import PipeLine, ScopedTiming# 设置图像处理分辨率常量# (Set image processing resolution constants)PICTURE_WIDTH = 160PICTURE_HEIGHT = 120# 初始化摄像头变量为空# (Initialize camera variable as None)sensor = None# 设置显示分辨率常量# (Set display resolution constants)DISPLAY_WIDTH = 640DISPLAY_HEIGHT = 480def scale_coordinates(data_tuple, target_resolution="640x480"): # 声明全局变量 # (Declare global variables) global PICTURE_WIDTH, PICTURE_HEIGHT """ 将160x120分辨率下的坐标元组等比例缩放到目标分辨率 (Scale coordinate tuple from 160x120 resolution proportionally to target resolution) 参数 (Parameters): data_tuple: 包含坐标信息的元组 (x1, y1, x2, y2) (Tuple containing coordinate information (x1, y1, x2, y2)) target_resolution: 目标分辨率,可选 "640x480" 或 "640x480" (Target resolution, optional "640x480" or "640x480") 返回 (Returns): 包含缩放后坐标的新元组 (x1, y1, x2, y2, length) (New tuple containing scaled coordinates (x1, y1, x2, y2, length)) """ # 检查输入类型,确保是至少包含4个元素的元组 # (Check input type, ensure it's a tuple with at least 4 elements) if not isinstance(data_tuple, tuple) or len(data_tuple) < 4: raise TypeError(f"期望输入至少包含4个元素的元组,但收到了 {type(data_tuple).__name__}") # (Expected a tuple with at least 4 elements, but received {type}) # 从元组中解析坐标点 # (Extract coordinates from the tuple) x1, y1, x2, y2 = data_tuple[:4] # 设置原始分辨率 # (Set source resolution) src_width, src_height = PICTURE_WIDTH, PICTURE_HEIGHT # 根据目标分辨率参数设置目标宽高 # (Set target width and height based on target resolution parameter) if target_resolution == "640x480": dst_width, dst_height = 640, 480 elif target_resolution == "640x480": # 注意:这里条件与上面相同,可能是代码错误 # (Note: this condition is the same as above, might be a code error) dst_width, dst_height = 640, 480 else: raise ValueError("不支持的分辨率,请使用 '640x480' 或 '640x480'") # (Unsupported resolution, please use '640x480' or '640x480') # 计算横向和纵向的缩放比例 # (Calculate horizontal and vertical scaling ratios) scale_x = dst_width / src_width scale_y = dst_height / src_height # 对坐标进行缩放,并四舍五入保证是整数 # (Scale coordinates and round to ensure integers) scaled_x1 = round(x1 * scale_x) scaled_y1 = round(y1 * scale_y) scaled_x2 = round(x2 * scale_x) scaled_y2 = round(y2 * scale_y) # 计算缩放后线段的长度(欧几里得距离) # (Calculate length of scaled line segment (Euclidean distance)) dx = scaled_x2 - scaled_x1 dy = scaled_y2 - scaled_y1 length = round((dx**2 + dy**2)**0.5) # 返回缩放后的坐标元组 # (Return tuple of scaled coordinates) return (scaled_x1, scaled_y1, scaled_x2, scaled_y2)# 设置显示模式为LCD# (Set display mode to LCD)display_mode = "LCD"# 创建图像处理Pipeline,设置RGB888格式尺寸和显示尺寸# (Create image processing pipeline with RGB888 format size and display size)pl = PipeLine(rgb888p_size=[640,360], display_size=[640,480], display_mode=display_mode)# 创建Pipeline实例,设置通道1的帧大小# (Create pipeline instance, set frame size for channel 1)pl.create(ch1_frame_size=[PICTURE_WIDTH,PICTURE_HEIGHT])# 主循环# (Main loop)while True: # 从通道1捕获图像 # (Capture image from channel 1) img = pl.sensor.snapshot(chn=CAM_CHN_ID_1) # 在图像中查找线段,合并距离为20,最大theta差异为5度 # (Find line segments in the image, merge distance 20, max theta difference 5 degrees) lines = img.find_line_segments(merge_distance=15, max_theta_diff=10) # 创建一个新的ARGB8888格式的图像用于显示 # (Create a new ARGB8888 format image for display) img = image.Image(640, 480, image.ARGB8888) # 清空图像 # (Clear the image) img.clear() # 遍历找到的所有线段 # (Iterate through all found line segments) for i, line in enumerate(lines): # 获取线段坐标并缩放到显示分辨率 # (Get line segment coordinates and scale to display resolution) line = scale_coordinates(line.line()) # 在图像上绘制红色线段,线宽为6 # (Draw red line on the image with thickness 6) img.draw_line(line, color=(255,0,0), thickness=6) # 在OSD3层显示图像 # (Display the image on OSD3 layer) Display.show_image(img, 0, 0, Display.LAYER_OSD3) # 短暂休眠微秒级延时,避免CPU过度占用 # (Brief microsecond sleep to avoid excessive CPU usage) time.sleep_us(1)本节代码的基本结构如下
导入和初始化部分:
导入了基础模块(time, os, sys, gc)用于系统操作和内存管理
导入了媒体相关模块,用于处理图像采集、显示等功能
导入了PipeLine库,用于图像处理Pipeline和性能计时
定义了两组分辨率常量:
scale_coordinates函数:
功能:将160x120分辨率下的坐标等比例转换到640x480分辨率
输入:包含坐标的元组(x1, y1, x2, y2)
处理步骤:
返回:转换后的坐标元组
图像处理Pipeline设置:
创建PipeLine实例,配置:
设置通道1的帧大小为160x120
主循环处理:
循环执行以下步骤:
从摄像头捕获图像
在图像中查找线段(合并距离15,最大角度差10度)
创建新的显示图像(640x480,ARGB8888格式)
对每个检测到的线段:
在显示层OSD3显示处理后的图像
短暂休眠以控制CPU使用率
这一节代码中,我们没有使用更常规的方式去做线段检测,而是借助K230支持多个图层的特性
用较低的分辨率去检测图形,然后放缩结果到高分辨的背景图像中
这样操作后,程序运行的帧率会显著的增高
本章后续的所有代码则用的是原始的检测方法,代码会更简单,但是帧率会降低不少
xxxxxxxxxximage.find_line_segments([roi[, merge_distance=0[, max_theta_difference=15]]])
使用霍夫转换来查找图像中的线段。返回一个 image.line 对象的列表。
roi 是一个用以复制的矩形的感兴趣区域(x, y, w, h)。如果未指定, ROI 即图像矩形。操作范围仅限于roi区域内的像素。
merge_distance 指定两条线段之间的可以相互分开而不被合并的最大像素数。
max_theta_difference 是上面 merge_distancede 要合并的的两个线段的最大角度差值。
此方法使用LSD库(也被OpenCV使用)来查找图像中的线段。这有点慢,但是非常准确,线段不会跳跃。
不支持压缩图像和bayer图像。
霍夫变换是一种从图像中检测直线、圆等几何形状的数学方法。
打个比方,想象你面前有一张散落着很多点的图纸。
如果要找出这些点能组成的直线:
- 传统方法是尝试连接任意两点看是否能形成直线,这样很费时
- 霍夫变换的思路是反过来 - 对每个点,假设它可能在无数条直线上,找到多个点共同落在的那条直线
举个生活中的例子:你站在路边看电线杆,虽然电线杆分布在不同位置,但是你一眼就能看出它们排成了一条直线。这就类似霍夫变换的原理。
实际应用
算法步骤