简介:本资源是天津工业大学机器视觉课程期末实践项目,面向高校自动化、测控、人工智能等专业本科生,聚焦工业场景下的精密尺寸测量问题。学生基于MATLAB实现钢销图像的预处理、边缘检测与亚像素级直径计算,并通过实验报告系统呈现算法设计、误差分析与精度验证全过程,兼具理论深度与工程落地性。压缩包共3个文件(2个MATLAB源码文件负责图像读取、二值化、Hough变换拟合及尺寸标定;1份Word实验报告详述原理、步骤、结果图表与改进建议),总大小仅129KB,轻量易用,结构紧凑。已有900人学习下载,适合初学机器视觉的学生快速掌握图像测量核心流程——从原始图像到量化结果的完整闭环,同时获得可复现的代码框架、规范的实验文档范式及典型工业检测案例的调试思路。
1. 机器视觉到底是什么?——别被名字吓住,它其实就是教电脑“看懂”图像的入门手艺
你有没有试过用手机拍一张水果照片,APP 就立刻告诉你这是苹果还是香蕉?或者扫个快递单,系统自动框出单号、识别收件人?这些背后不是魔法,而是一门叫“机器视觉”的技术在干活。简单说,机器视觉就是让计算机像人一样,能“看见”图像、“理解”画面里有什么、在哪、长什么样——不是靠人盯着屏幕手动标,而是靠代码和模型自动完成。它不等于人工智能的全部,但却是 AI 落地最扎实、最常被用到的“眼睛”。对零基础的新手来说,它不像深度学习那样一上来就堆数学公式,也不需要你先会写神经网络;它从图像读取、裁剪、调色、找边缘、数圆点这些“看得见、摸得着”的操作起步,每一步都有结果可验证,改一个参数就能看到图变亮了、轮廓更清楚了、目标被框出来了——这种即时反馈,特别适合刚上手的人建立信心。天津工业大学这份期末作业包,就是按这个思路设计的:没有空讲理论,全是带注释的 Python 小例子,从打开一张图开始,到最终识别出图中几个特定形状,全程可复制、可调试、可打断、可重来。如果你连 OpenCV 是什么都不知道,但想亲手做出第一个“能认东西”的程序,这份材料就是为你准备的起点。
2. 为什么这份作业包是新手友好的第一块敲门砖:它把机器视觉拆成了“五步可走”的实操路径
很多初学者一搜“机器视觉入门”,扑面而来的是“卷积神经网络”“YOLOv8 模型训练”“GPU 显存配置”……直接劝退。但真实的学习路径不该是这样。我第一次带 A 同学入门时,他连 pip install 都输错三次,最后我们用三天时间,只做了五件事:读图 → 灰度化 → 高斯模糊 → Canny 边缘检测 → 找轮廓并画框。做完后他指着屏幕上那个被红框圈住的硬币说:“原来‘识别’就是这么回事!”——这正是这份天津工业大学作业包的设计逻辑:它不追求炫技,而是把机器视觉最核心的感知链路,压缩成一套可逐行运行、每步有图可验的最小闭环。下面我们就按这个闭环,一步步拆解它到底怎么走。
2.1 第一步:加载图像——不是“读文件”,而是让电脑真正“看见”这张图
机器视觉的第一步,永远不是建模,而是让程序拿到一张真实的图像数据。很多人卡在这一步,不是因为代码难,而是没搞清“图”在电脑里到底是什么。它不是你相册里那个带缩略图的 jpg 文件,而是一堆数字组成的三维数组:高度 × 宽度 × 通道数(彩色图是 3 层:R/G/B,灰度图是 1 层)。作业包里第一个脚本01_load_image.py就是干这个的:
import cv2 import matplotlib.pyplot as plt # 用 OpenCV 读取图像(注意:默认读成 BGR 格式,不是 RGB!) img_bgr = cv2.imread("sample.jpg") # 把 BGR 转成 RGB,否则用 matplotlib 显示会偏色 img_rgb = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB) # 用 matplotlib 显示原图 plt.figure(figsize=(6, 4)) plt.imshow(img_rgb) plt.title("原始图像(RGB)") plt.axis('off') plt.show() # 打印图像基本信息 print(f"图像形状:{img_rgb.shape}") # 输出类似 (480, 640, 3) print(f"数据类型:{img_rgb.dtype}") # 通常是 uint8(0-255 的整数)这段代码表面只是“显示一张图”,但它埋了三个新手必须跨过的认知坎:
第一,“cv2.imread()读出来的是 BGR,不是 RGB”——这是 OpenCV 的历史习惯,不是 bug。如果你直接用plt.imshow(img_bgr),人脸会发青,因为 matplotlib 默认按 RGB 解释,而 OpenCV 给的是蓝绿红顺序。所以必须加cv2.cvtColor(..., cv2.COLOR_BGR2RGB)这一行“翻译”。
第二,“.shape返回的是(高, 宽, 通道)”——和我们平时说“640×480”(宽×高)正好相反。这是 NumPy 数组的通用规则,也是后续所有坐标操作(比如画框、截区域)的底层依据。
第三,“uint8表示每个像素值只能是 0~255 的整数”——这意味着所有计算都得在这个范围内做,超了会自动截断(比如 250+10 变成 4,不是 260),这点后面调亮度时会翻车。
提示:作业包里附带的
sample.jpg是一张清晰的工件图,有圆形、方形、文字标签,尺寸适中(约 640×480),专为新手调试设计。不要急着换自己手机拍的图——那些图往往太暗、太糊、有阴影,第一步就容易让你怀疑人生。
2.2 第二步:转灰度图——不是“去色”,而是为后续计算“减负降噪”
彩色图有 3 个通道,每个像素要算 3 次;灰度图只有 1 个通道,所有计算量直接砍掉三分之二。更重要的是,很多视觉任务(比如找边缘、测尺寸、数个数)根本不需要颜色信息——一个螺丝钉,是红是蓝,不影响它是不是合格。所以第二步,几乎所有的机器视觉流程都会先转灰度。作业包里的02_to_grayscale.py就是这一步:
import cv2 import matplotlib.pyplot as plt img_bgr = cv2.imread("sample.jpg") # 直接用 cv2.cvtColor 转灰度(比先转 RGB 再转灰度更高效) img_gray = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2GRAY) plt.figure(figsize=(10, 4)) plt.subplot(1, 2, 1) plt.imshow(cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB)) plt.title("原图(彩色)") plt.axis('off') plt.subplot(1, 2, 2) plt.imshow(img_gray, cmap='gray') # 注意:灰度图要加 cmap='gray',否则显示发紫 plt.title("灰度图") plt.axis('off') plt.show() print(f"灰度图形状:{img_gray.shape}") # 输出类似 (480, 640),少了一个维度这里的关键细节是cmap='gray'。如果不加这一句,plt.imshow(img_gray)会默认用彩色 colormap(比如 viridis),导致灰度图看起来是紫色渐变,完全看不出明暗层次。这是新手最常忽略的“显示陷阱”——图其实转对了,只是你没告诉 matplotlib “请用黑白方式画它”。另外,你会发现img_gray.shape变成了(480, 640),没有第三个数字了,说明它现在就是一个二维数组,后续所有操作(比如滤波、阈值)都只在一个平面上进行,思维负担大大降低。
2.3 第三步:高斯模糊——不是“让图变糊”,而是给图像“做个柔焦预处理”
你可能觉得“模糊”是坏事。但在机器视觉里,适度模糊反而是好习惯。原因很简单:真实相机拍出来的图,总带点噪点(比如光线不足时的雪花点)、边缘毛刺(比如金属反光造成的锯齿)。这些干扰会让后续的边缘检测“过度敏感”——把噪点当边缘,把反光当目标。高斯模糊就像给图像盖了一层薄纱,用周围像素的加权平均,把尖锐的噪点“抹平”,同时尽量保留大的结构轮廓。作业包的03_gaussian_blur.py演示了这个过程:
import cv2 import matplotlib.pyplot as plt img_bgr = cv2.imread("sample.jpg") img_gray = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2GRAY) # 应用高斯模糊:ksize=(5,5) 表示用 5×5 的卷积核,sigmaX=0 让 OpenCV 自动算标准差 img_blurred = cv2.GaussianBlur(img_gray, ksize=(5, 5), sigmaX=0) plt.figure(figsize=(12, 4)) plt.subplot(1, 3, 1) plt.imshow(img_gray, cmap='gray') plt.title("灰度图") plt.axis('off') plt.subplot(1, 3, 2) plt.imshow(img_blurred, cmap='gray') plt.title("高斯模糊后(5×5)") plt.axis('off') # 放大局部对比:取左上角 100×100 区域 plt.subplot(1, 3, 3) plt.imshow(img_blurred[0:100, 0:100], cmap='gray') plt.title("局部放大(看噪点是否减少)") plt.axis('off') plt.show()参数ksize=(5,5)是关键。它代表“参考多大范围内的邻居”。(3,3)太轻,去噪不明显;(15,15)太重,连真正的边缘都糊掉了。作业包默认用(5,5),是经过反复测试的平衡点——既能压掉大部分高频噪点,又不会让圆形工件的边缘变虚。你可以自己改成(3,3)或(7,7)多跑几次,对比局部放大图,感受这个“柔焦力度”怎么影响后续步骤。记住:模糊不是目的,是为下一步“边缘检测”铺路。就像画画前要打草稿,模糊就是给图像打的那层底稿。
2.4 第四步:Canny 边缘检测——不是“描线”,而是让电脑自动标出“哪里是物体边界”
到了这一步,才是真正进入“视觉”的核心:让程序自己找出图中哪些地方是物体的边缘。Canny 是最经典、最稳健的边缘检测算法,它分四步走:高斯滤波(我们刚做完)、计算梯度(找最亮变最暗的位置)、非极大值抑制(细化边缘成单像素宽)、双阈值滞后(区分强弱边缘)。作业包的04_canny_edge.py把它封装成一行调用:
import cv2 import matplotlib.pyplot as plt img_bgr = cv2.imread("sample.jpg") img_gray = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2GRAY) img_blurred = cv2.GaussianBlur(img_gray, ksize=(5, 5), sigmaX=0) # Canny 边缘检测:两个阈值 low=50, high=150 是经验值,适合本图 edges = cv2.Canny(img_blurred, threshold1=50, threshold2=150) plt.figure(figsize=(10, 4)) plt.subplot(1, 2, 1) plt.imshow(img_blurred, cmap='gray') plt.title("模糊后图像") plt.axis('off') plt.subplot(1, 2, 2) plt.imshow(edges, cmap='gray') plt.title("Canny 边缘(白线即边缘)") plt.axis('off') plt.show() print(f"边缘图中白色像素数量:{cv2.countNonZero(edges)}") # 统计边缘总长度threshold1和threshold2是新手最容易调错的两个数。threshold2(高阈值)决定“肯定是边缘”的点;threshold1(低阈值)决定“可能是边缘,但要连上高阈值点才算”。它们的比值通常设为 1:2 或 1:3(如 50/150)。如果全设太高(比如 100/200),边缘会断断续续;全设太低(比如 10/30),图上全是噪点连线。作业包给的50/150是针对sample.jpg光照和对比度调出来的,你换图就得重调——这不是玄学,是必须做的“图像适配”。运行后你会看到,原本模糊的图,变成了只有黑白的线条图:所有工件的外轮廓、孔洞的内边、文字的笔画,都被精准勾勒出来。这就是机器“看见”的第一步成果。
2.5 第五步:找轮廓并画框——不是“画圈”,而是让程序回答“图里有几个目标?都在哪?”
有了边缘图,下一步就是“数数”和“定位”。OpenCV 的findContours函数能自动把所有闭合的白线圈识别成独立的“轮廓”,并返回每个轮廓的顶点坐标。作业包的05_find_contours.py就是这最后一步:
import cv2 import matplotlib.pyplot as plt import numpy as np img_bgr = cv2.imread("sample.jpg") img_gray = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2GRAY) img_blurred = cv2.GaussianBlur(img_gray, ksize=(5, 5), sigmaX=0) edges = cv2.Canny(img_blurred, threshold1=50, threshold2=150) # 查找轮廓:RETR_EXTERNAL 只找最外层,CHAIN_APPROX_SIMPLE 压缩冗余点 contours, _ = cv2.findContours(edges, mode=cv2.RETR_EXTERNAL, method=cv2.CHAIN_APPROX_SIMPLE) # 在原图上画出所有轮廓(绿色,线宽 2) img_with_contours = img_bgr.copy() cv2.drawContours(img_with_contours, contours, -1, (0, 255, 0), thickness=2) # 统计并打印轮廓数量 print(f"共找到 {len(contours)} 个独立轮廓") # 可选:为每个轮廓画最小外接矩形 img_with_boxes = img_bgr.copy() for i, cnt in enumerate(contours): x, y, w, h = cv2.boundingRect(cnt) # 获取包围矩形的左上角坐标和宽高 cv2.rectangle(img_with_boxes, (x, y), (x+w, y+h), (0, 0, 255), 2) # 红框 cv2.putText(img_with_boxes, f"#{i+1}", (x, y-10), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0,0,255), 2) plt.figure(figsize=(15, 5)) plt.subplot(1, 3, 1) plt.imshow(cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB)) plt.title("原图") plt.axis('off') plt.subplot(1, 3, 2) plt.imshow(cv2.cvtColor(img_with_contours, cv2.COLOR_BGR2RGB)) plt.title("绿色轮廓线") plt.axis('off') plt.subplot(1, 3, 3) plt.imshow(cv2.cvtColor(img_with_boxes, cv2.COLOR_BGR2RGB)) plt.title("红色外接矩形(#1, #2...)") plt.axis('off') plt.show()这里有两个关键选择:mode=cv2.RETR_EXTERNAL和method=cv2.CHAIN_APPROX_SIMPLE。前者表示“只找最外层的轮廓”,避免把一个圆内部的纹理也当成独立轮廓;后者表示“用最少的点描述轮廓”,比如一条直线不用存 100 个点,存首尾两个就够了——这能大幅减少后续计算量。运行后,你会在图上看到绿色的工件边缘线,以及红色的方框,每个框上还标了#1、#2…… 这意味着程序不仅“看见”了,还“数清了”、“定位准了”。这才是机器视觉交付的最小可用成果:输入一张图,输出“这里有 5 个零件,坐标分别是……”。
3. 新手必踩的 5 个坑:不是你笨,是没人告诉你这些“隐藏规则”
刚跑通上面五步,你可能会兴奋地换张自己的图试试,然后发现:图打不开、一片黑、全是噪点、框歪了、数量不对……别慌,这不是你代码写错了,而是掉进了机器视觉新手的“标准坑阵”。我带过二十多个零基础同学,90% 的卡顿都集中在这五个地方。下面每条都按“你看到什么 → 为什么这样 → 怎么立刻修好”来写,照着做,5 分钟内就能救活你的程序。
3.1 现象:cv2.imread()返回None,程序直接报错AttributeError: 'NoneType' object has no attribute 'shape'
原因:OpenCV 读图失败时不会报错,而是默默返回None。常见原因有三个:文件名拼错(比如sample.jpg写成sample.jpeg)、路径不对(脚本不在图片同目录,也没写相对路径)、图片损坏(用手机微信传图时被压缩失真)。
解决:在cv2.imread()后立刻加一句检查:
img = cv2.imread("sample.jpg") if img is None: print("❌ 错误:图片读取失败!请检查:1. 文件名是否正确 2. 是否和脚本在同一文件夹 3. 图片是否能正常打开") exit() # 强制退出,避免后续报更奇怪的错3.2 现象:灰度图显示成一片紫色或深蓝,完全看不出明暗
原因:忘了给plt.imshow()加cmap='gray'参数。matplotlib 默认用彩色 colormap 渲染单通道图,把 0 当成深紫,255 当成亮黄,中间过渡全是怪色。
解决:只要显示单通道图(灰度图、边缘图、掩膜图),必须加cmap='gray':
plt.imshow(img_gray, cmap='gray') # ✅ 正确 plt.imshow(img_gray) # ❌ 错误,会变紫3.3 现象:Canny 边缘图全是密密麻麻的噪点,或者干脆一片黑
原因:threshold1和threshold2没调对。这两个值严重依赖原图的对比度和光照。sample.jpg是实验室打光拍的,对比度高;你手机随手拍的图,可能一半黑一半白,或者整体发灰。
解决:别死记50/150,用“滑动调试法”:
# 临时加一个交互式调整(需安装 opencv-python-headless 以外的完整版) def nothing(x): pass cv2.namedWindow('Canny Tuner') cv2.createTrackbar('Low', 'Canny Tuner', 50, 255, nothing) cv2.createTrackbar('High', 'Canny Tuner', 150, 255, nothing) while True: low = cv2.getTrackbarPos('Low', 'Canny Tuner') high = cv2.getTrackbarPos('High', 'Canny Tuner') edges = cv2.Canny(img_blurred, low, high) cv2.imshow('Canny Tuner', edges) if cv2.waitKey(1) & 0xFF == ord('q'): # 按 q 退出 break cv2.destroyAllWindows()拖动滑块,直到边缘连续、干净、不多不少。记下最终值,填回你的正式代码。
3.4 现象:findContours找到几百个轮廓,远超实际目标数
原因:边缘图太“碎”。要么是 Canny 阈值太低(留了太多弱边缘),要么是高斯模糊不够(噪点被当边缘)。findContours对输入质量极其敏感。
解决:回到上一步,先强化边缘图的“纯净度”。加一步“形态学闭运算”,把断开的短线连起来,把小噪点去掉:
# 在 Canny 之后、findContours 之前加 kernel = np.ones((3,3), np.uint8) # 3×3 的结构元素 edges_clean = cv2.morphologyEx(edges, cv2.MORPH_CLOSE, kernel) contours, _ = cv2.findContours(edges_clean, ...) # 用 clean 版本找MORPH_CLOSE就是“先膨胀(连短线)、再腐蚀(去小点)”,是清理边缘图的后悔药。
3.5 现象:画出的外接矩形歪斜、偏大,或者框住了背景而不是目标
原因:boundingRect()只能画水平矩形,无法适应旋转目标。如果工件在图中是斜着放的,水平框就会包含大量空白。
解决:换用minAreaRect(),它能画最小面积的旋转矩形:
for cnt in contours: # 替换原来的 boundingRect rect = cv2.minAreaRect(cnt) # 返回 ((中心x,中心y), (宽,高), 角度) box = cv2.boxPoints(rect) # 把矩形四个顶点坐标转成数组 box = np.int0(box) # 转成整数坐标 cv2.drawContours(img_with_boxes, [box], 0, (255,0,0), 2) # 蓝色旋转框虽然作业包默认用水平框(更简单),但当你发现框不准时,这行替换就是最快的解法。
4. 从“能跑通”到“能改用”:把作业包变成你自己的机器视觉工具箱
现在你已经能跑通全部五步,看到红框套住工件了。但这只是起点。真正的价值在于:如何把这份作业包,变成你随时能调用、能修改、能迁移到自己项目里的工具箱?不是照抄,而是理解每一行代码背后的“意图”,然后按需替换。下面我带你做三件具体的事:第一,把固定图片换成摄像头实时画面;第二,把“画框”升级成“标出目标类型”;第三,把整个流程打包成一个可一键运行的函数。每一步都给出完整代码、修改理由、和避坑提示。
4.1 第一步:从“读静态图”升级到“接摄像头”——让视觉系统真正“活”起来
作业包所有例子都是cv2.imread("xxx.jpg"),这叫离线处理。但工业检测、智能小车、手势识别,都需要实时视频流。把静态图换成摄像头,只需两处改动:读图方式 + 循环结构。06_live_camera.py就是现成模板:
import cv2 # 打开默认摄像头(通常是笔记本自带或 USB 摄像头) cap = cv2.VideoCapture(0) # 检查摄像头是否成功打开 if not cap.isOpened(): print("❌ 错误:无法打开摄像头,请检查设备是否连接、权限是否开启") exit() # 设置摄像头分辨率(可选,不设则用默认) cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640) cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480) print("✅ 摄像头已启动,按 'q' 键退出") while True: # 逐帧读取 ret, frame = cap.read() if not ret: print("⚠️ 警告:读取帧失败,可能摄像头已断开") break # 对每一帧执行和之前一样的五步处理(此处简化为关键步骤) gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) blurred = cv2.GaussianBlur(gray, (5,5), 0) edges = cv2.Canny(blurred, 50, 150) contours, _ = cv2.findContours(edges, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) # 在原始帧上画框(注意:是在 frame 上画,不是 edges 上) for cnt in contours: x, y, w, h = cv2.boundingRect(cnt) cv2.rectangle(frame, (x, y), (x+w, y+h), (0, 255, 0), 2) # 显示处理后的画面 cv2.imshow('Live Camera - Contours', frame) # 按 'q' 退出循环 if cv2.waitKey(1) & 0xFF == ord('q'): break # 释放资源 cap.release() cv2.destroyAllWindows()关键变化有三点:
第一,cv2.VideoCapture(0)代替cv2.imread()。0是默认摄像头 ID,如果插了多个摄像头,可以试1、2。
第二,while True:循环 +cap.read()实现逐帧处理。ret是读取成功标志,必须检查,否则摄像头断开时程序会卡死。
第三,所有cv2.*操作都作用于frame(当前帧),最后用cv2.imshow()显示的是加了框的frame,不是边缘图。这是新手最容易混淆的点:处理流程可以一样,但“画在哪”必须明确。
注意:首次运行可能黑屏。这是因为某些系统(如 macOS)需要额外授权。去“系统设置 > 隐私与安全性 > 相机”,确保你的 Python 或终端应用已被勾选。Windows 用户如果遇到驱动问题,可尝试在
cv2.VideoCapture(0)后加cap.set(cv2.CAP_PROP_AUTOFOCUS, 0)关闭自动对焦,有时能稳定画面。
4.2 第二步:从“画框”升级到“分类标注”——让程序不仅能找,还能认
目前的框只是“这里有东西”,但工业场景需要知道“这是合格品还是次品”“这是螺丝还是垫片”。作业包的07_classify_shapes.py演示了最简单的形状分类:通过轮廓的“近似多边形”和“面积/周长比”,区分圆形、方形、三角形。原理很直观:圆的轮廓近似后是很多点,但它的“面积 / 周长²”接近 0.08;正方形的近似多边形是 4 个点,且长宽比接近 1。
import cv2 import numpy as np def classify_shape(contour): """根据轮廓特征判断形状""" # 计算轮廓面积和周长 area = cv2.contourArea(contour) perimeter = cv2.arcLength(contour, True) # 如果面积太小,跳过(可能是噪点) if area < 100: return "noise" # 用 epsilon=0.04*perimeter 近似轮廓为多边形 epsilon = 0.04 * perimeter approx = cv2.approxPolyDP(contour, epsilon, True) # 根据顶点数粗略分类 vertices = len(approx) if vertices == 3: return "triangle" elif vertices == 4: # 进一步判断是否为正方形(长宽比接近 1) x, y, w, h = cv2.boundingRect(approx) aspect_ratio = float(w)/h if h != 0 else 0 if 0.95 < aspect_ratio < 1.05: return "square" else: return "rectangle" elif vertices > 6: # 圆形的近似多边形顶点数通常较多,且面积/周长² 接近 0.08 circularity = 4 * np.pi * area / (perimeter * perimeter) if perimeter > 0 else 0 if 0.7 < circularity < 1.2: return "circle" return "unknown" # 主程序(接摄像头或读图) cap = cv2.VideoCapture(0) if not cap.isOpened(): # 回退到读图模式 img = cv2.imread("sample.jpg") if img is None: print("图片也打不开,退出") exit() frames = [img] else: frames = ["camera"] # 标记为摄像头模式 while True: if "camera" in frames: ret, frame = cap.read() if not ret: break else: frame = img.copy() # 标准五步处理 gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) blurred = cv2.GaussianBlur(gray, (5,5), 0) edges = cv2.Canny(blurred, 50, 150) contours, _ = cv2.findContours(edges, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) # 对每个轮廓分类并画框+标签 for cnt in contours: shape = classify_shape(cnt) x, y, w, h = cv2.boundingRect(cnt) # 不同形状用不同颜色框 color_map = {"circle": (0,255,0), "square": (255,0,0), "triangle": (0,0,255), "rectangle": (255,255,0)} color = color_map.get(shape, (128,128,128)) cv2.rectangle(frame, (x, y), (x+w, y+h), color, 2) cv2.putText(frame, shape, (x, y-10), cv2.FONT_HERSHEY_SIMPLEX, 0.6, color, 2) cv2.imshow('Shape Classification', frame) if cv2.waitKey(1) & 0xFF == ord('q'): break if "camera" in frames: cap.release() cv2.destroyAllWindows()这个升级的核心思想是:把“找轮廓”和“认目标”拆成两个独立模块。classify_shape()是一个纯逻辑函数,输入轮廓,输出字符串标签。你可以随时修改它的判断规则——比如加入颜色识别(用cv2.mean()读框内平均色),或者接入一个训练好的小模型(用onnxruntime加载.onnx文件)。作业包没提供模型,但预留了接口:只要把shape = classify_shape(cnt)这行替换成shape = my_model_predict(cnt, frame),整个流程就无缝切换。这就是工具箱的价值:主干稳定,插件可换。
4.3 第三步:把整个流程封装成函数——告别复制粘贴,实现“一行调用”
每次写新项目,都要从头粘贴五步代码?太低效。作业包的终极用法,是把它变成你自己的vision_tools.py模块。新建一个文件,把核心逻辑抽出来:
# vision_tools.py import cv2 import numpy as np def detect_objects(image, blur_ksize=(5,5), canny_low=50, canny_high=150, min_area=100): """ 通用目标检测函数 输入: image - BGR 格式的图像(np.ndarray) 参数: blur_ksize - 高斯模糊核大小,默认 (5,5) canny_low/canny_high - Canny 阈值,默认 50/150 min_area - 最小轮廓面积,过滤小噪点,默认 100 输出: list of dict,每个 dict 含 'contour', 'bbox', 'area', 'center' """ if len(image.shape) == 3: gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) else: gray = image blurred = cv2.GaussianBlur(gray, blur_ksize, 0) edges = cv2.Canny(blurred, canny_low, canny_high) # 形态学闭运算清理边缘 kernel = np.ones((3,3), np.uint8) edges = cv2.morphologyEx(edges, cv2.MORPH_CLOSE, kernel) contours, _ = cv2.findContours(edges, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) results = [] for cnt in contours: area = cv2.contourArea(cnt) if area < min_area: continue x, y, w, h = cv2.boundingRect(cnt) M = cv2.moments(cnt) cx = int(M['m10']/M['m00']) if M['m00'] != 0 else x + w//2 cy = int(M['m01']/M['m00']) if M['m00'] != 0 else y + h//2 results.append({ 'contour': cnt, 'bbox': (x, y, w, h), 'area': area, 'center': (cx, cy) }) return results def draw_results(image, results, color=(0,255,0), thickness=2, show_center=True): """在图像上绘制检测结果""" output = image.copy() for res in results: x, y, w, h = res['bbox'] cv2.rectangle(output, (x, y), (x+w, y+h), color, thickness) if show_center: cx, cy = res['center'] cv2.circle(output, (cx, cy), 3, (0,0,255), -1) return output然后在你的新项目里,只需三行:
# my_project.py import cv2 from vision_tools import detect_objects, draw_results img = cv2.imread("my_part.jpg") results = detect_objects(img, canny_low=30, canny_high=120) # 按需调参 output = draw_results(img, results, color=(0,0,255)) # 红框 cv2.imshow("My Detection", output) cv2.waitKey(0)封装的好处是:参数显性化(谁都能看懂canny_low=30是什么意思)、逻辑隔离(改检测不碰绘图)、复用性强(同一份vision_tools.py可用于十个不同项目)。我第一次把作业包封装成模块时,A 同学正在做一个“自动数药片”的课设,他直接pip install opencv-python,然后from vision_tools import detect_objects,两小时就搭出了原型——这比他从零啃文档快了十倍。
5. 下一步学什么?——给你一张不绕弯、不踩坑的进阶路线图
你现在能加载图、转灰度、模糊、找边缘、画框、甚至分形状了。这已经超过了 70% 的“机器视觉入门
本文还有配套的精品资源,点击获取