庐山派K230-CanMV开发板实战:基于LSD与霍夫变换的图像特征检测(线段、矩形、圆形)

庐山派K230-CanMV开发板实战:基于LSD与霍夫变换的图像特征检测(线段、矩形、圆形) 庐山派K230-CanMV开发板实战基于LSD与霍夫变换的图像特征检测线段、矩形、圆形最近有不少朋友在用庐山派K230开发板做视觉项目时问我怎么快速检测图像里的基本形状比如识别路牌上的圆形、定位文档里的矩形框或者找出画面中的直线。这些基础的特征检测其实是很多高级视觉应用的基石。今天我就结合庐山派K230和CanMV框架带大家手把手实现线段、矩形和圆形的检测。我会把官方文档里那些有点“干”的API说明掰开揉碎了讲再配上完整的、能直接跑的代码。咱们不光是调个函数更要理解背后的LSD算法和霍夫变换是怎么一回事以及实际项目中怎么调参才能让检测更准、更快。1. 准备工作与环境搭建在开始写代码之前咱们得先把开发环境准备好。庐山派K230的CanMV框架用起来挺方便的但有几个地方需要注意我踩过的坑你就别踩了。1.1 硬件连接与显示模式选择首先确保你的摄像头和屏幕已经正确连接到开发板。代码里需要根据你用的显示设备来初始化官方例程给了三种模式VIRT(虚拟显示器)如果你只用IDE的“缓冲区”来看图像帧率可能会受USB带宽限制有点卡。LCD(3.1寸屏幕)这是最常用的模式用随板附赠的立创3.1寸屏幕扩展板。HDMI(HDMI输出)如果你接了HDMI显示器就用这个模式。在代码开头你需要设置DISPLAY_MODE变量。咱们教程里默认用LCD模式。1.2 图像格式与API限制这里有个非常重要的点CanMV里大部分图像处理API包括咱们今天要用的三个检测函数都只支持RGB565或GRAYSCALE灰度格式的图像。不支持压缩图像和Bayer图像。所以初始化摄像头时务必用sensor.set_pixformat(Sensor.RGB565)来设置像素格式。如果格式不对函数会报错或者检测不出来东西。1.3 基础代码框架下面这段代码是今天所有例程的“骨架”它完成了摄像头初始化、图像捕获和显示的基本流程。你先不用深究每一行知道它是干什么的就行后面我们会把检测代码“塞”进这个循环里。# 立创·庐山派-K230-CanMV开发板资料与相关扩展板软硬件资料官网全部开源 # 开发板官网www.lckfb.com # 技术支持常驻论坛任何技术问题欢迎随时交流学习 # 立创论坛www.jlc-bbs.com/lckfb # 关注bilibili账号【立创开发板】掌握我们的最新动态 # 不靠卖板赚钱以培养中国工程师为己任 import time, os, sys from media.sensor import * from media.display import * from media.media import * # 设置捕获图像的分辨率可以根据性能调整 picture_width 400 picture_height 240 sensor_id 2 # 摄像头ID通常为2 sensor None # 重要根据你的显示设备修改这里 DISPLAY_MODE LCD # 可选 VIRT, LCD, HDMI # 根据显示模式设置对应的屏幕分辨率 if DISPLAY_MODE VIRT: DISPLAY_WIDTH ALIGN_UP(1920, 16) DISPLAY_HEIGHT 1080 elif DISPLAY_MODE LCD: DISPLAY_WIDTH 800 DISPLAY_HEIGHT 480 elif DISPLAY_MODE HDMI: DISPLAY_WIDTH 1920 DISPLAY_HEIGHT 1080 else: raise ValueError(未知的 DISPLAY_MODE请选择 VIRT, LCD 或 HDMI) try: # 1. 初始化摄像头 sensor Sensor(idsensor_id) sensor.reset() # 设置输出图像尺寸和格式必须是RGB565 sensor.set_framesize(widthpicture_width, heightpicture_height, chnCAM_CHN_ID_0) sensor.set_pixformat(Sensor.RGB565, chnCAM_CHN_ID_0) # 2. 初始化显示 if DISPLAY_MODE VIRT: Display.init(Display.VIRT, widthDISPLAY_WIDTH, heightDISPLAY_HEIGHT, fps60) elif DISPLAY_MODE LCD: Display.init(Display.ST7701, widthDISPLAY_WIDTH, heightDISPLAY_HEIGHT, to_ideTrue) elif DISPLAY_MODE HDMI: Display.init(Display.LT9611, widthDISPLAY_WIDTH, heightDISPLAY_HEIGHT, to_ideTrue) # 3. 启动媒体管道 MediaManager.init() sensor.run() # 4. 主循环不断抓图、处理、显示 while True: os.exitpoint() # 用于响应IDE的停止信号 # 从摄像头通道0捕获一帧图像 img sensor.snapshot(chnCAM_CHN_ID_0) # 特征检测的代码将放在这里 # 将处理后的图像显示到屏幕中央 Display.show_image(img, xint((DISPLAY_WIDTH - picture_width) / 2), yint((DISPLAY_HEIGHT - picture_height) / 2)) except KeyboardInterrupt as e: print(用户停止: , e) except BaseException as e: print(f异常: {e}) finally: # 5. 程序退出时清理资源 if isinstance(sensor, Sensor): sensor.stop() Display.deinit() os.exitpoint(os.EXITPOINT_ENABLE_SLEEP) time.sleep_ms(100) MediaManager.deinit()好了舞台搭好了接下来咱们请三位“主角”登场。2. 线段检测用LSD算法找出图像中的直线线段检测有什么用比如做车道线检测、文档扫描对齐、工业上的边缘定位第一步往往就是找出图像里所有的直线段。2.1 算法原理霍夫变换与LSD庐山派K230的find_line_segments函数用的是LSD (Line Segment Detector)算法这个算法在OpenCV里也有应用特点是检测准确线段位置稳定不会乱跳。它的核心思想可以简单理解成两步空间变换它用一种叫“霍夫变换”的方法把图像里所有可能是边缘的点映射到另一个参数空间比如极坐标去投票。在参数空间里得票高的“位置”就对应着图像里的一条直线。线段合并实际图像中的一条长直线可能被算法拆成好几段检测出来。LSD算法会用两个参数来判断这些线段是不是应该合并成一条merge_distance两条线段中心点距离小于这个值单位像素就考虑合并。max_theta_difference两条线段的角度差小于这个值单位度才考虑合并。2.2 手把手代码实现现在我们把检测代码加到主循环里。找到上面代码骨架中# 特征检测的代码将放在这里 这个位置用下面的代码替换它。# --- 线段检测部分开始 --- # 使用LSD算法查找线段 # merge_distance20: 中心点距离20像素以内的线段考虑合并 # max_theta_diff10: 角度差10度以内的线段考虑合并 lines img.find_line_segments(merge_distance20, max_theta_diff10) line_count 0 # 初始化计数器 print(------线段统计开始------) for line in lines: # 在图像上绘制检测到的线段颜色为蓝色粗细为3像素 img.draw_line(line.line(), color(1, 147, 230), thickness3) # 打印线段的具体坐标信息 (x1, y1, x2, y2) print(fLine {line_count}: {line}) line_count 1 print(f共检测到 {line_count} 条线段) print(---------END---------) # --- 线段检测部分结束 ---代码解释img.find_line_segments(merge_distance20, max_theta_diff10)这就是核心的检测函数。返回一个列表里面每个元素都是一个line对象。line.line()这个方法返回线段的起点和终点坐标(x1, y1, x2, y2)正好给draw_line函数用。循环遍历所有检测到的线段用蓝色画出来并在串口终端打印信息。2.3 运行效果与调试把摄像头对准一个有明显直线的场景比如电脑屏幕上显示的这个文档边框运行程序后你会在3.1寸屏幕或IDE缓冲区上看到图像并且蓝色的线段会被画出来。同时串行终端会打印类似这样的信息------线段统计开始------ Line 0: (50, 100, 350, 100) Line 1: (350, 100, 350, 300) ... ---------END---------这表示它检测到了几条线段以及它们的坐标。3. 矩形检测用AprilTag算法定位四边形矩形检测的用途更直接比如扫码枪要先找到二维码的矩形位置或者OCR识别前先把文本段落框出来。3.1 算法原理AprilTag的四边形检测find_rects函数用的是AprilTag 二维码检测库中的四边形检测算法。这个算法很强大即使矩形被旋转、缩放甚至有点扭曲仿射变换它也能较好地识别出来。但它有个前提矩形和背景的对比度要比较明显。它主要靠一个参数来过滤噪点threshold这个阈值控制检测到的矩形边缘的“强度”下限。值太小可能会把一些噪声误认为矩形值太大可能漏掉真正的、但对比度不高的矩形。需要根据实际图像调整。3.2 手把手代码实现同样替换主循环中的检测部分。# --- 矩形检测部分开始 --- # 使用AprilTag四边形检测算法查找矩形 # threshold5000: 边缘强度阈值低于此值的矩形会被过滤掉 rects img.find_rects(threshold5000) rect_count 0 print(------矩形统计开始------) for rect in rects: # 绘制矩形框 img.draw_rectangle(rect.rect(), color(1, 147, 230), thickness3) # 打印矩形的信息 (x, y, w, h) print(fRect {rect_count}: {rect}) # 如果想获取矩形的四个顶点坐标顺时针可以使用 rect.corners() # corners rect.corners() # print(fCorners: {corners}) rect_count 1 print(f共检测到 {rect_count} 个矩形) print(---------END---------) # --- 矩形检测部分结束 ---代码解释rect.rect()返回矩形的左上角坐标和宽高(x, y, w, h)。rect.corners()这是一个更详细的方法返回一个包含四个(x, y)元组的列表按顺时针顺序代表了矩形的四个角。这在需要精确几何变换时很有用。3.3 运行效果对准一个清晰的矩形比如屏幕上的这个方框运行后矩形会被蓝色框标出终端会输出矩形的位置和大小。4. 圆形检测用霍夫变换寻找圆环圆形检测可以用来找交通标志、工业零件上的圆孔、棋盘上的棋子等等。4.1 算法原理霍夫圆变换find_circles函数使用的是经典的霍夫圆变换。它的原理和找直线的霍夫变换类似但参数空间更复杂圆心x, y和半径r。算法会先对图像进行索贝尔Sobel滤波来增强边缘然后在参数空间里找“投票”最多的组合那就是圆。这个函数参数比较多咱们重点看几个x_stride,y_stride检测时在x和y方向跳过的像素数。如果你知道要检测的圆很大可以增大这个值来加快速度。threshold和矩形检测类似这是圆的“边缘强度”阈值。值越大只检测边缘更明显的圆。x_margin,y_margin,r_margin合并参数。因为图像可能有噪声同一个圆可能被检测出多个相近的结果。这三个参数分别设置了圆心x坐标、y坐标和半径的合并容差小于这个容差的检测结果会被合并成一个。4.2 手把手代码实现替换主循环中的检测代码。# --- 圆形检测部分开始 --- # 使用霍夫变换查找圆形 # threshold6000: 圆的边缘强度阈值 # 其他参数如x_stride, y_stride等使用默认值已知圆较大时可调整以提速 circles img.find_circles(threshold6000) circle_count 0 print(------圆形统计开始------) for circle in circles: # 绘制圆形 img.draw_circle(circle.circle(), color(1, 147, 230), thickness3) # 打印圆的信息 (x, y, r) 圆心坐标和半径 print(fCircle {circle_count}: {circle}) circle_count 1 print(f共检测到 {circle_count} 个圆形) print(---------END---------) # --- 圆形检测部分结束 ---代码解释circle.circle()返回圆的圆心坐标和半径(x, y, r)。4.3 运行效果找一个圆形图案比如这个运行程序蓝色的圆圈就会画在检测到的圆上。5. 实战调优与避坑指南代码跑起来不难但要想在实际项目里用好调参和预处理是关键。下面是我总结的几个经验。5.1 效果不好先做多角度测试特征检测算法很受环境干扰。光线太暗、反光、拍摄角度倾斜、物体离得太远或太近都会影响效果。距离问题随板送的摄像头是定焦的。物体太近会模糊太远特征又太小。如果项目需要可以买个调焦工具稍微用点力旋转镜头就能手动调焦。多做测试一定要在你项目预期的各种光照、角度、距离下多测试了解算法的边界在哪里。5.2 核心参数调优心法光调用默认参数往往不够得根据你的场景微调。检测类型关键参数调参心法线段检测merge_distance值太小同一条直线可能被分成好几段值太大可能把两条本不相关的线段合并。根据图像中线段间的实际距离来设。max_theta_difference控制合并线段的角度容差。只合并角度非常接近的线段。矩形检测threshold这是最重要的参数。慢慢调整如果画面中矩形框很清晰、对比度高就用大值如10000如果框比较淡就调小如2000。目的是过滤掉噪声又不漏掉真目标。圆形检测threshold和矩形类似控制圆的边缘强度阈值。圆越清晰、对比越强这个值可以越大。x_margin,y_margin,r_margin当画面抖动或分辨率低时同一个圆可能被重复检测多次。适当调大这些合并容差比如从10调到20可以让结果更稳定。x_stride,y_stride已知目标圆很大时比如占画面1/4可以设为4或5能显著提升检测速度。调参口诀从小开始逐步增加观察变化找到拐点。5.3 使用ROI提升效率与准确性这三个检测函数都支持roi参数。roi(x, y, w, h)可以指定只检测图像的某一块区域。减少干扰如果你的目标只出现在画面某个固定区域比如流水线上零件经过的位置设置ROI可以避免其他区域的杂物被误检。提升速度只处理一小块区域计算量大大减少帧率自然就上去了。例如如果你知道圆形只出现在图像中央一个200x200的区域内可以这样写circles img.find_circles(roi(100, 80, 200, 200), threshold6000)5.4 别忘了图像预处理官方例程为了演示算法直接在原图上检测。但在真实项目中预处理往往能决定成败。如果图像噪声多可以先做个高斯滤波。如果目标边缘不明显可以先进行边缘检测如Canny或二值化。如果光照不均可以先做直方图均衡化或调整对比度/亮度。你可以把上一章学到的预处理方法放在img sensor.snapshot()之后检测函数调用之前。比如先转灰度、再二值化然后再找矩形效果可能会好很多。好了关于庐山派K230上基本的几何特征检测就聊到这里。代码都给你了原理也讲了调参的坑也指出来了。接下来就靠你动手去试了。记住视觉算法没有“一招鲜”多调、多试、多根据实际场景优化才是硬道理。遇到问题可以去立创论坛看看那里有很多热心工程师在交流。