
简介本资源是一套基于YOLOv5与OpenCV实现的多目标形状识别完整项目面向深度学习初学者及计算机视觉实践者解决图像中7类常见几何形状圆形、矩形、菱形、多边形、五角星、三角形、梯形的精准检测与颜色关联分析问题适用于教学演示、课程设计或轻量级工业质检场景。压缩包共473个文件含418张PNG与23张JPG格式标注图像、7个Jupyter Notebook训练/推理脚本、7个Python工具模块、4个CSV标签文件及1个已训练好的.pt模型辅以README说明与Git版本管理文件结构清晰、开箱即用。目前已有670人学习下载。用户可直接复现端到端流程从数据加载、模型微调、可视化检测到结果导出无需额外标注配套Notebook详细注释训练参数与OpenCV后处理逻辑并提供手动构建的200样本高质量形状数据集兼顾实用性与教学可解释性。1. 为什么用 YOLOv5 OpenCV 做形状识别比传统图像处理稳得多——7 类几何体在杂乱背景下的实时定位与分类实测你有没有试过用cv2.HoughCircles()检测圆形结果光照一变、边缘一模糊模型就“失明”或者用cv2.findContours()cv2.approxPolyDP()判别多边形但当两个三角形紧挨着、一个正方形被轻微旋转、甚至纸张褶皱导致轮廓断裂时len(approx)直接从 4 跳到 7分类逻辑全线崩溃这不是你代码写得差是传统图像处理在多目标、低对比、非理想光照、轻微形变场景下天然的天花板。而这篇笔记要讲的是一个真实落地过的方案用YOLOv5s 版本做端到端的多目标检测 分类输入是普通 RGB 图像无需灰度化/二值化输出是每个目标的 bounding box shape classcircle, square, triangle, rectangle, pentagon, hexagon, star不依赖轮廓提取、不依赖霍夫变换、不依赖先验几何约束。它背后不是魔法而是把“形状”当作视觉语义类别来学——就像识别猫狗一样识别圆和五角星。整套流程跑通只需 3 小时数据标注LabelImg、格式转换VOC → YOLO、训练train.py、推理OpenCV DNN 加载.pt或.onnx、后处理NMS 可视化。它适合嵌入式部署树莓派5实测 12 FPS、工业质检金属件轮廓识别、教育实验学生课程设计可直接复现也避开了所有需要调参玄学的环节。下面我们从零开始把这套“形状即类别”的思路变成你电脑里能跑、能改、能部署的硬核代码。2. 为什么选 YOLOv5 而不是 YOLOv8 或 Faster R-CNN——轻量、成熟、OpenCV 兼容性决定实战上限2.1 YOLOv5 是当前形状识别任务的“甜点模型”精度、速度、生态三平衡YOLOv5 在 2023–2024 年的实际工业项目中依然是多目标形状识别类任务的首选基线模型。不是因为它最新而是因为它的三个不可替代性OpenCV DNN 模块原生支持.onnx导出且无兼容陷阱YOLOv5 的export.py生成的 ONNX 模型OpenCV 4.5.5 可直接cv2.dnn.readNetFromONNX()加载无需 TensorRT、ONNX Runtime 等额外依赖而 YOLOv8 官方导出的 ONNX 存在 dynamic axes 和 custom op如NonMaxSuppressionOpenCV 无法解析必须用onnx-simplifier二次处理且简化后仍可能丢失 anchor 信息导致 bbox 偏移。训练稳定性远超 YOLOv8 的默认配置YOLOv5 的hyp.scratch-low.yaml超参数组合lr00.01, lrf0.1, momentum0.937, weight_decay0.0005对小样本形状数据集1000 张收敛极稳YOLOv8 默认lr00.01但lrf0.01学习率衰减过猛在 7 类形状这种细粒度分类上容易早停mAP0.5 波动 ±3%。模型体积与推理延迟可控YOLOv5s约 14MB.pt转 ONNX 后仅 22MBCPU 推理i5-10210U单帧 42msYOLOv5m26MB在树莓派54GB RAM VideoCore VII GPU上启用 OpenCV 的DNN_BACKEND_INFERENCE_ENGINE后达 11.8 FPS而 YOLOv8m ONNX 即使简化后仍超 45MB树莓派5 上 CPU 推理卡在 5 FPS 且内存溢出。提示本项目采用ultralytics8.0.20YOLOv5 最终稳定版非yolov5pip 包已废弃而是从 https://github.com/ultralytics/yolov5 clone v8.0.20 tag。不要用pip install yolov5—— 它指向一个无人维护的 forktrain.py会报AttributeError: Namespace object has no attribute cfg。2.2 数据集结构决定模型泛化能力7 类形状的标注逻辑与边界规避本项目配套数据集共 1247 张图像覆盖 7 类形状circle,square,triangle,rectangle,pentagon,hexagon,star。关键不是数量而是标注策略每张图至少含 3 个目标最多 12 个且强制存在遮挡与尺度变化例如一张图中一个star部分被square遮挡一个triangle以 30° 旋转一个circle在阴影区边缘。这迫使模型学习形状的纹理结构上下文而非单纯记忆轮廓。背景高度多样化白纸、木纹桌、水泥地、打印纸褶皱、带噪点的手机拍摄图模拟产线相机抖动杜绝“白底黑形”的过拟合陷阱。尺寸归一化陷阱规避所有标注框.txt文件使用 YOLO 格式class_id center_x center_y width height归一化到 0~1但训练前不进行--rect矩形推理YOLOv5 默认--rect会将图像 resize 成 640×640 的矩形区域导致rectangle类别因宽高比失真而漏检本项目强制--rect False用--img 640保持原始长宽比 padding确保rectangle与square的宽高比特征可区分。数据集目录结构严格遵循 YOLO 规范shapes_dataset/ ├── images/ │ ├── train/ # 998 张 │ ├── val/ # 125 张 │ └── test/ # 124 张 ├── labels/ │ ├── train/ # 对应 .txt 标注 │ ├── val/ │ └── test/ └── shapes.yaml # 关键定义 nc: 7, names: [circle,square,...]shapes.yaml内容必须精确匹配train: ../images/train val: ../images/val test: ../images/test nc: 7 names: [circle, square, triangle, rectangle, pentagon, hexagon, star]注意names顺序必须与.txt中class_id0~6完全一致否则训练时类别错位circle可能被当成star。2.3 OpenCV 在整个 pipeline 中的真实角色不只是后处理更是部署锚点很多人误以为 OpenCV 在 YOLO 项目里只干两件事读图、画框。但在本方案中OpenCV 承担了三重核心职能训练后模型压缩与跨平台部署枢纽用torch.onnx.export()导出 ONNX再用cv2.dnn.readNetFromONNX()加载——这是目前唯一能在 Windows/Linux/Raspberry Pi 上零依赖、纯 Python、免编译运行 YOLO 推理的方式。实时视频流的低开销预处理cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)cv2.resize(frame, (640,640))cv2.dnn.blobFromImage()全程在 CPU 上完成比 PyTorch 的transforms快 3.2 倍实测 i5-10210U。后处理 NMS 的确定性保障YOLOv5 原生 NMS 使用 Torch 的torchvision.ops.nms但 OpenCV 的cv2.dnn.NMSBoxes()输入为(x,y,w,h)坐标 score输出为索引列表不依赖 CUDA、不引入随机 seed、结果绝对可复现避免多人测试时 mAP 波动。因此本项目不使用yolov5.detect()而是构建 OpenCV 原生推理链net cv2.dnn.readNetFromONNX(yolov5s_shapes.onnx) blob cv2.dnn.blobFromImage(img, 1/255.0, (640,640), (0,0,0), swapRBTrue, cropFalse) net.setInput(blob) outs net.forward(net.getUnconnectedOutLayersNames()) # 后续用 cv2.dnn.NMSBoxes 处理 outs[0]batch1, 25200, 11这个链条就是你在树莓派、Jetson Nano、甚至 Windows 10 笔记本上不装 CUDA、不配 Conda 环境也能跑通的底线方案。3. 从零训练数据准备、环境配置、训练命令与关键参数调优3.1 环境配置Conda 创建最小依赖环境避坑 PyTorch 与 OpenCV 版本冲突YOLOv5 训练对环境极其敏感尤其torch与opencv-python-headless的 ABI 兼容性。以下命令创建经 12 次翻车验证的稳定环境conda create -n yolov5-shapes python3.8 conda activate yolov5-shapes pip install torch1.13.1cpu torchvision0.14.1cpu torchaudio0.13.1 -f https://download.pytorch.org/whl/torch_stable.html pip install opencv-python-headless4.8.1.78 # 必须指定此版本4.9.x 会导致 cv2.dnn.blobFromImage() 返回空 blob pip install numpy1.23.5 pandas1.5.3 matplotlib3.7.1 git clone https://github.com/ultralytics/yolov5 cd yolov5 git checkout tags/v8.0.20 pip install -e .注意opencv-python-headless4.8.1.78是关键。新版 OpenCV 4.9.x 在blobFromImage()中修改了内存布局YOLOv5 的models/common.py中Detect层的forward()会因输入 tensor shape 不匹配而报RuntimeError: expected scalar type Float but found Half。降级至此版本问题消失。3.2 数据集转换VOC 标注 → YOLO 格式LabelImg 生成的 XML 转 TXT本项目数据集提供的是 VOC 格式.xml需转为 YOLO 格式.txt。不要用网上流传的“万能转换脚本”——它们常忽略rotation和occluded字段导致triangle旋转 45° 时 bbox 错位。我们用xml_to_yolo.py已包含在源码包中核心逻辑# xml_to_yolo.py 关键片段 def convert_voc_to_yolo(xml_path, img_w, img_h, classes): tree ET.parse(xml_path) root tree.getroot() yolo_lines [] for obj in root.findall(object): cls_name obj.find(name).text.strip() if cls_name not in classes: continue cls_id classes.index(cls_name) bbox obj.find(bndbox) xmin int(bbox.find(xmin).text) ymin int(bbox.find(ymin).text) xmax int(bbox.find(xmax).text) ymax int(bbox.find(ymax).text) # 归一化中心点 宽高非左上右下 x_center (xmin xmax) / 2.0 / img_w y_center (ymin ymax) / 2.0 / img_h width (xmax - xmin) / img_w height (ymax - ymin) / img_h yolo_lines.append(f{cls_id} {x_center:.6f} {y_center:.6f} {width:.6f} {height:.6f}) return yolo_lines执行命令python xml_to_yolo.py \ --voc_dir ./VOCdevkit/VOC2024/Annotations \ --img_dir ./VOCdevkit/VOC2024/JPEGImages \ --out_dir ./shapes_dataset/labels/train \ --classes circle,square,triangle,rectangle,pentagon,hexagon,star血泪经验img_w和img_h必须取自对应图像的cv2.imread().shape[:2]不能硬编码 640×640否则不同分辨率图像 bbox 归一化错误训练时 loss 爆表100。3.3 训练命令与超参数7 类形状的 3 个必调参数YOLOv5 默认train.py参数对通用目标检测友好但对几何形状这种高相似度、低纹理差异的类别需针对性调整。以下是本项目验证有效的最小命令python train.py \ --img 640 \ --batch 16 \ --epochs 150 \ --data shapes.yaml \ --weights yolov5s.pt \ --name shapes_yolov5s \ --cache ram \ --rect False \ --hyp data/hyps/hyp.scratch-low.yaml \ --optimizer AdamW \ --cos-lr \ --exist-ok其中3 个必调参数详解--rect False如前所述禁用矩形推理保留原始长宽比 padding防止rectangle与square特征混淆--hyp data/hyps/hyp.scratch-low.yaml该文件将mosaic概率从 1.0 降至 0.5mixup从 0.1 降至 0.0关闭所有图像混合增强——因为star和pentagon在 mosaic 中易被裁剪变形模型学到的是“拼图伪影”而非真实形状--optimizer AdamW相比默认SGDAdamW 在小数据集上收敛更快且weight_decay0.0005与AdamW的 L2 正则化协同更好防止circle与ellipse未标注过拟合。训练过程监控重点train/box_loss应在 50 epoch 内降至 0.05若 0.15 持续 20 epoch说明数据标注有误检查rectangle是否标成squareval/mAP0.5在 120 epoch 达峰值本项目实测 0.892之后缓慢下降此时--patience 30自动停止val/precision与val/recall差值 0.08表明各类别均衡若star的 recall 仅 0.62说明其标注框太小20px需在shapes.yaml中加scale1.2扩大 anchor。4. 避坑YOLOv5OpenCV 形状识别的 5 个血泪现场与根治方案4.1 现象OpenCV 加载 ONNX 后net.forward()返回空数组[]原因OpenCV 版本 4.8.1.78 且模型导出时未固定 dynamic axes。YOLOv5export.py默认dynamic_axes{images: {0: batch, 2: height, 3: width}}但 OpenCV 4.9 无法解析height/width动态维度。解决导出 ONNX 时强制静态尺寸python export.py --weights yolov5s_shapes.pt --include onnx --imgsz 640 640 --dynamic False并在export.py第 122 行附近将dynamic_axes改为None再运行。4.2 现象训练时val/mAP0.5卡在 0.3~0.4val/box_loss降不下去原因shapes.yaml中train/val/test路径写错或labels/下.txt文件名与images/下.jpg名不一一对应如img_001.jpg对应img_001.xml但labels/里是img_001.txt—— 正确若为001.txt则缺失。YOLOv5 不报错静默跳过该样本。解决运行校验脚本check_dataset.pyimport os img_dir ./shapes_dataset/images/train label_dir ./shapes_dataset/labels/train img_files set([f.split(.)[0] for f in os.listdir(img_dir)]) label_files set([f.split(.)[0] for f in os.listdir(label_dir)]) print(Missing in labels:, img_files - label_files) print(Missing in images:, label_files - img_files)确保输出为空集。4.3 现象推理时cv2.dnn.NMSBoxes()返回空列表或只返回 1 个框原因conf_thres置信度阈值设得过高如 0.7而形状识别因纹理单一模型输出 score 普遍偏低0.3~0.6。解决在推理代码中将conf_thres设为 0.25iou_thres设为 0.45YOLOv5 默认boxes [] # 存 (x,y,w,h) scores [] # 存 conf for out in outs[0]: # out shape: (25200, 11) scores.append(float(out[4])) # 第5列是 objectness boxes.append([int(out[0]), int(out[1]), int(out[2]-out[0]), int(out[3]-out[1])]) indices cv2.dnn.NMSBoxes(boxes, scores, 0.25, 0.45) # 关键0.254.4 现象star类别几乎不被检测pentagon与hexagon经常混淆原因star在数据集中样本数最少仅 142 张且其尖角易在 JPEG 压缩中丢失导致特征弱。解决在data/hyps/hyp.scratch-low.yaml中将cls_pw分类损失权重从 0.5 提升至 0.8强化类别区分对star图像做PIL.ImageEnhance.Sharpness().enhance(2.0)预处理再保存为 PNG避免 JPEG 压缩损角在train.py的__getitem__中对star类别样本强制mosaicFalse防止尖角被裁剪。4.5 现象树莓派5 上cv2.dnn.readNetFromONNX()报cv2.error: OpenCV(4.8.1) ... error: (-215:Assertion failed) ...原因树莓派5 的 ARM64 架构与 OpenCV 预编译 wheel 不兼容readNetFromONNX加载失败。解决不用pip install opencv-python-headless改用源码编译sudo apt update sudo apt install -y build-essential cmake libgtk-3-dev libcanberra-gtk3-module python3-dev wget -O opencv.zip https://github.com/opencv/opencv/archive/4.8.1.zip unzip opencv.zip cd opencv-4.8.1 mkdir build cd build cmake -D CMAKE_BUILD_TYPERELEASE \ -D CMAKE_INSTALL_PREFIX/usr/local \ -D OPENCV_DNN_ONNXON \ -D PYTHON3_EXECUTABLE/usr/bin/python3 \ -D BUILD_TESTSOFF .. make -j4 sudo make install sudo ldconfig编译后cv2.__version__应为4.8.1且cv2.dnn.readNetFromONNX()正常。5. OpenCV 推理全流程从摄像头读取到形状标签可视化含完整可运行代码5.1 ONNX 模型导出与优化生成 OpenCV 友好格式训练完成后进入yolov5目录执行python export.py \ --weights runs/train/shapes_yolov5s/weights/best.pt \ --include onnx \ --imgsz 640 640 \ --dynamic False \ --opset 12 \ --simplify--simplify调用onnx-simplifier自动移除冗余节点。生成best.onnx后用 Netron 查看输入名应为images非input输出名应为output3 个 tensor取output[0]。若输出名为381等数字则--simplify失败需手动安装onnx-simplifier0.4.34并重试。5.2 实时摄像头推理代码detect_shapes_opencv.py以下为完整可运行代码Python 3.8, OpenCV 4.8.1import cv2 import numpy as np import time # 加载 ONNX 模型 net cv2.dnn.readNetFromONNX(runs/train/shapes_yolov5s/weights/best.onnx) net.setPreferableBackend(cv2.dnn.DNN_BACKEND_OPENCV) # 强制 CPU # net.setPreferableTarget(cv2.dnn.DNN_TARGET_CPU) # 树莓派5 可启用 # 类别名称必须与 shapes.yaml 一致 classes [circle, square, triangle, rectangle, pentagon, hexagon, star] colors [(255,0,0), (0,255,0), (0,0,255), (255,255,0), (255,0,255), (0,255,255), (128,128,0)] cap cv2.VideoCapture(0) # 0 为默认摄像头 cap.set(cv2.CAP_PROP_FRAME_WIDTH, 1280) cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 720) while True: ret, frame cap.read() if not ret: break # 预处理BGR→RGB→resize→blob rgb_frame cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) resized cv2.resize(rgb_frame, (640, 640)) blob cv2.dnn.blobFromImage(resized, 1/255.0, (640,640), (0,0,0), swapRBTrue, cropFalse) # 推理 net.setInput(blob) start_time time.time() outs net.forward(net.getUnconnectedOutLayersNames()) # 输出 shape: (1, 25200, 11) infer_time time.time() - start_time # 解析输出 boxes [] confidences [] class_ids [] h, w frame.shape[:2] for out in outs[0]: scores out[5:] # 7 个类别置信度 class_id np.argmax(scores) confidence scores[class_id] if confidence 0.25: # 置信度阈值 # 还原 bbox 到原始尺寸 center_x, center_y, width, height out[0:4] x int((center_x - width/2) * w / 640) y int((center_y - height/2) * h / 640) w_box int(width * w / 640) h_box int(height * h / 640) boxes.append([x, y, w_box, h_box]) confidences.append(float(confidence)) class_ids.append(class_id) # NMS indices cv2.dnn.NMSBoxes(boxes, confidences, 0.25, 0.45) # 绘制 if len(indices) 0: for i in indices.flatten(): x, y, w_box, h_box boxes[i] label f{classes[class_ids[i]]}: {confidences[i]:.2f} color colors[class_ids[i]] cv2.rectangle(frame, (x, y), (xw_box, yh_box), color, 2) cv2.putText(frame, label, (x, y-10), cv2.FONT_HERSHEY_SIMPLEX, 0.6, color, 2) # 显示帧率与推理时间 fps 1.0 / infer_time if infer_time 0 else 0 cv2.putText(frame, fFPS: {fps:.1f}, (10,30), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0,255,0), 2) cv2.putText(frame, fInfer: {infer_time*1000:.0f}ms, (10,60), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0,255,0), 2) cv2.imshow(Shapes Detection, frame) if cv2.waitKey(1) ord(q): break cap.release() cv2.destroyAllWindows()逻辑说明outs[0]是 (1, 25200, 11) 的 tensor其中 25200 3×(80×80 40×40 20×20) 是 YOLOv5s 的 anchor 总数11 4(bbox) 1(objectness) 7(classes)。代码中out[0:4]取 bbox 坐标out[5:]取 7 个类别概率np.argmax()得到预测类别。x, y, w_box, h_box的还原公式* w / 640是关键——因为模型在 640×640 上训练但摄像头输出是 1280×720必须按比例缩放。5.3 推理性能实测表格不同硬件上的 FPS 与精度硬件平台OpenCV 版本模型输入尺寸FPS平均mAP0.5test set备注i5-10210U (Win10)4.8.1.78yolov5s640×64023.60.892CPU 推理无 GPU 加速Raspberry Pi 54.8.1源码编译yolov5s640×64011.80.871启用DNN_BACKEND_INFERENCE_ENGINEJetson Nano4.5.4yolov5s640×64018.30.885CUDA backendTensorRT 加速RTX 3060 Laptop4.8.1.78yolov5s640×640127.40.895CUDA backendFP16 推理注意树莓派5 的 11.8 FPS 是开启 Intel OpenVINO IE backend 的结果net.setPreferableBackend(cv2.dnn.DNN_BACKEND_INFERENCE_ENGINE)若仅用 CPU backendFPS 为 6.2。Jetson Nano 的 CUDA backend 需安装libglib2.0-0和libglib2.0-dev否则cv2.dnn.readNetFromONNX()报错。6. 进阶技巧如何让star和pentagon的识别率从 82% 提升到 96%——基于后处理的几何验证闭环6.1 为什么纯 CNN 会混淆star和pentagon——特征空间的坍缩陷阱YOLOv5 的 backboneCSPDarknet53提取的是全局语义特征而star与pentagon在 CNN 的浅层 feature map 上边缘响应高度相似都是 5 个尖角主频能量分布接近。模型只能依赖最后的 classification head 做微弱区分导致star的 recall 仅 0.82test set。这不是模型能力不足而是任务定义缺陷我们把“形状”当作黑匣子类别却忽略了形状的本质是几何属性——角点数、内角、凸性。因此最佳方案不是换更大模型而是CNN 几何验证的两级判决。6.2 几何验证闭环用 OpenCV 提取轮廓并计算角点数cv2.approxPolyDP在 YOLO 推理得到 bbox 后对每个检测框 ROI 做精细化几何分析# 在 detect_shapes_opencv.py 的绘制循环内追加 for i in indices.flatten(): x, y, w_box, h_box boxes[i] roi frame[y:yh_box, x:xw_box] # 截取 ROI gray cv2.cvtColor(roi, cv2.COLOR_BGR2GRAY) blurred cv2.GaussianBlur(gray, (5,5), 0) edged cv2.Canny(blurred, 50, 150) contours, _ cv2.findContours(edged, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) if len(contours) 0: continue cnt max(contours, keycv2.contourArea) # 取最大轮廓 epsilon 0.02 * cv2.arcLength(cnt, True) approx cv2.approxPolyDP(cnt, epsilon, True) num_corners len(approx) # 修正 CNN 预测仅当 CNN 预测为 star/pentagon/hexagon 时触发 if class_ids[i] in [0,4,5,6]: # circle, pentagon, hexagon, star if class_ids[i] 0 and num_corners 8: # circle 应近似为圆corner8 pass elif class_ids[i] 4 and num_corners 5: # pentagon 必须 5 角 pass elif class_ids[i] 5 and num_corners 6: # hexagon 必须 6 角 pass elif class_ids[i] 6 and num_corners 10: # star 是 10 角5 外尖5 内凹 pass else: # 不匹配降级为更鲁棒类别 if num_corners 3: class_ids[i] 2 # triangle elif num_corners 4: class_ids[i] 3 # rectangle elif num_corners 5: class_ids[i] 4 # pentagon优先信任几何 elif num_corners 10: class_ids[i] 6 # star # 其他情况维持原预测这段代码在 CNN 输出后用传统 CV 方法验证角点数形成“CNN 初筛 CV 精判”的闭环。实测将star的 recall 从 0.82 提升至 0.96pentagon从 0.85 提升至 0.94且circle的 precision 保持 0.99几何验证不干扰高置信度圆形。6.3 参数调优表epsilon与Canny阈值的黄金组合形状类别epsilon系数CannylowCannyhigh适用场景circle0.0430100光滑边缘抗噪声star0.01550180尖本文还有配套的精品资源点击获取