
简介这是一份基于OpenCV与Python实现的瓶口缺陷检测项目面向毕业设计、课程设计及图像处理初学者提供完整可运行的源码与真实瓶口/瓶筐图像数据集。项目通过对比正常与异常瓶口图像聚焦边缘检测、轮廓提取与缺陷分类等关键环节难度适中适合快速上手或作为课题拓展。资源包共44个文件大小约4.99MB其中包含2个Python脚本主检测程序与图像生成脚本、40张PNG测试样本涵盖瓶口、瓶筐不同形态、1份使用说明txt及环境配置文件结构清晰安装依赖后即可运行。已有224人学习下载代码经本地编译测试搭配目录说明与样例数据可帮助读者直观理解缺陷检测流程、验证算法效果并复现实验结果对学习OpenCV图像处理与工业质检场景均有较高参考价值。1. 瓶口缺陷检测为什么OpenCVPython比想象中更难瓶口缺陷检测是工业视觉里最容易被低估的场景瓶口就一个几十毫米的圆环看起来比焊缝检测、PCB检测简单得多可一旦转到实际产线反光、瓶口毛边、输送带抖动、环境光变化都会让一枚NG品溜过去。用传统OpenCVPython去做难点不在“会不会用函数”而在“怎么把成像条件和检测逻辑绑在一起”。这个标题里的项目源码本质上是把采集、定位、缺陷判定、参数调优串成了一条可复现的流水线而真正要解决的事是把这条流水线里最常见的工程取舍讲清楚。适用人群是正在做视觉项目、需要快速在本地跑通瓶口缺陷检测方案的工程师也适合刚接触OpenCV图像处理、想用一个小而完整的数据集练手的人。先立一个反直觉的结论瓶口检测用深度学习不一定比传统视觉更划算后者的优势是可解释、可调参、算力要求低产线能看懂、能接手。2. 用OpenCVPython搭建瓶口缺陷检测的成像与预处理链路2.1 成像采集先定好相机的瓶口缺陷检测位置不管源码包里用的是什么相机瓶口缺陷检测第一步都得先把“瓶口”变成一张高对比度的图。常见做法是采用俯视位相机光轴与瓶口平面垂直外加环形无影光源让瓶口边缘形成暗-亮-暗的清晰过渡。没有固定光源的情况下尽量选背光或低角度打光避免瓶口内壁的反光直接在图像上烧成一片白。采集环节要注意三个参数曝光时间、光圈和增益。曝光时间控制运动模糊产线速度越快曝光越短光圈影响景深瓶口有高低差时不要全开增益则是最后手段增益越高图像噪声越大后面预处理要花更多力气去压。我一般会先用自动曝光跑一轮拿到稳定值后再手动固定否则程序每次启动都会重新适应亮度缺陷检测的阈值就一直在漂。源码包里如果带采集脚本多数是用OpenCV的VideoCapture或工业相机的SDK拉流。假如你只是先跑通逻辑也可以直接用cv2.imread逐张读图先把图像处理链路调稳再去接实时流。2.2 预处理三步灰度化、去噪、增强瓶口缺陷检测的图像预处理目标只有一个让缺陷信号尽量突出同时把背景纹理压下去。灰度化如果采集是彩色图先转灰度后续所有阈值处理都在单通道上做。去噪用高斯模糊或中值滤波去掉传感器噪声和光源颗粒。瓶口上的细小灰尘会被误判成缺陷这一步很关键。增强如果瓶口与背景对比度不足用直方图均衡化或对比度拉伸。import cv2 import numpy as np def preprocess_bottle_neck(img): # 转为灰度图所有后续的阈值处理都在单通道上做 gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 高斯模糊核大小取奇数sigma0 让OpenCV按核大小自动计算 blur cv2.GaussianBlur(gray, (5, 5), 0) # 直方图均衡化增强灰度分布让瓶口边缘和缺陷更容易分离 enhanced cv2.equalizeHist(blur) # 也可以换成CLAHE局部增强适合光源偏暗、照度不均的场景 # clahe cv2.createCLAHE(clipLimit2.0, tileGridSize(8, 8)) # enhanced clahe.apply(gray) return enhanced高斯模糊的核大小直接决定噪声抑制力度核越大越平滑但瓶口边缘也会被磨掉一圈裂纹这类细缺陷会变淡。核设为(5, 5)是比较常见的折中如果要保留头发丝级别的裂纹换成(3, 3)甚至先模糊再叠加原图细节。直方图均衡化的好处是自动坏处是会把不均匀的光照放大所以如果现场光源稳定反而可以不做均衡化。预处理环节常见参数影响灰度化cv2.COLOR_BGR2GRAY丢失颜色信息计算量降为1/3高斯模糊核(5, 5), sigma0核越大缺陷边缘越模糊直方图均衡化默认全局增强对比度但对光照不均敏感CLAHE局部增强clipLimit2.0, 8x8网格局部对比度增强适合低照度预处理这部分的参数会直接影响后面所有阈值。记住一个原则预处理是给定位和缺陷检测“垫”的宁可用保守参数保留信息不要在预处理阶段就把缺陷抹掉。提示把预处理单独封装成函数后面调优时只需要改这一个入口调试定位和缺陷检测时能少改一半代码。3. 瓶口定位与ROI分割瓶口缺陷检测的几何基础3.1 用CannyHoughCircles找到瓶口外圆缺陷检测不能对着整幅图扫那样会把瓶口以外的干扰全算进来。常见做法是先定位瓶口外圆再做ROI分割。OpenCV里最直接的是HoughCircles但它对参数敏感直接用容易找错圆。我的流程是先Canny提边缘再HoughCircles找圆。Canny的两个阈值和高斯模糊的核大小要配合——边缘越干净Hough结果越稳定。HoughCircles有几个关键参数dp是累加器分辨率与图像分辨率的比值param1是Canny高阈值param2是圆心投票阈值值越大要求越严格漏检率升高误检率降低minRadius和maxRadius是搜索半径范围。瓶口直径在图像里占比一般是知道的把这两个值卡住就能去掉大量误检。def find_bottle_neck_center(img, min_r, max_r): # 先在预处理图上做Canny边缘越细圆心越准 edges cv2.Canny(img, 50, 150) # 找圆dp1.2param240半径范围由实际像素直径决定 circles cv2.HoughCircles( edges, cv2.HOUGH_GRADIENT, dp1.2, minDistimg.shape[0], # 图里只允许出现一个瓶口 param1150, param240, minRadiusmin_r, maxRadiusmax_r ) if circles is None: return None # 返回 (cx, cy, radius)取票数最高的一组 return circles[0][0].astype(int)这里有一个容易踩的坑HoughCircles的minDist如果太小会把同一个圆检测出多个结果直接设成图像高度表示全图只出一个瓶口。如果现场一瓶图里同时出现多个瓶子minDist要按瓶间距设置而不是盲目调大。param1设为150是可用起点边缘线太碎时调高轮廓断掉时调低。定位到圆心后下一步是从圆心按半径向外取一个环形区域这个环就是后续缺陷检测的ROI。3.2 极坐标展开把圆环变成长条直接在圆环上做模板匹配有个问题瓶口的凹槽、台阶在图像里是圆弧形的形态学结构元素很难匹配曲率。常见做法是把环形ROI按极坐标展开成矩形条带OpenCV的warpPolar可以直接做。展开后原本的圆弧缺陷变成横向或纵向的线状缺陷检测逻辑大幅简化。def polar_unwrap(img, cx, cy, r_in, r_out): # 展开角度范围设为360度半径范围设为环的宽度 width int(np.pi * (r_in r_out)) height r_out - r_in return cv2.warpPolar( img, dsize(width, height), center(cx, cy), maxRadiusr_out, flagscv2.WARP_POLAR_LINEAR | cv2.INTER_LINEAR )这里有个值得注意的细节warpPolar的dsize如果尺寸不对展开后的图会变形。一般我会把高度设为环宽长度按圆周长换算这样横轴是角度、纵轴是半径。展开后水平方向的条带代表圆周方向垂直方向代表径向后续缺陷检测就可以用标准矩形卷积核做形态学处理这是瓶口缺陷检测项目里很常见的提速手段。如果不想用极坐标展开也可以直接在环形mask内做形态学但结构元素就得用环形或扇形不够灵活遇到倾斜的瓶口还容易误伤展开后还有一个附带好处能把瓶口旋转对齐方便和模板图做逐像素差分。3.3 定位失败的兜底边缘拟合与轮廓筛选HoughCircles偶尔会失败尤其是瓶口有深色标注、液体泡沫附着时。兜底方案是用阈值分割加轮廓筛选先对灰度图做阈值提取瓶口暗区再按轮廓的面积、周长、圆度筛出外圆。def fit_circle_by_contour(img): # 二值化瓶口和背景对比度足够时Otsu自动找阈值 _, binary cv2.threshold(img, 0, 255, cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU) contours, _ cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) if not contours: return None # 按面积排序取最大的轮廓再拟合最小外接圆 largest max(contours, keycv2.contourArea) (x, y), radius cv2.minEnclosingCircle(largest) return int(x), int(y), int(radius)轮廓拟合的稳定性上限取决于阈值分割的质量。如果瓶口内侧还有高光反射得先做形态学闭运算填掉内部的小孔。这个方法比HoughCircles更快但要求光源稳定两者可以组成串联策略先用HoughCircles返回None或圆度不合理时再切到轮廓拟合。我在项目里会把两个结果同时画到调试窗口上让现场人员一眼看出当前用的是哪条路径。4. 瓶口缺陷检测的核心裂纹、缺口与毛刺的判定4.1 三类缺陷的像素特征参数瓶口缺陷检测要关注的主要是三类裂纹、缺口、毛刺。它们在图像里的特征完全不同缺陷类型像素表现检测思路裂纹细长暗线沿径向或周向延伸边缘增强后按长度和方向过滤缺口瓶口边缘局部凹陷与标准轮廓比较计算局部径向偏差毛刺边缘外侧凸起的亮块差分后按面积和凸度过滤这三类缺陷有一个共同点正常瓶口是高度规则的回转体所以在展开图里背景非常平滑。缺陷本质上是与模板的偏差。基于这个思路最稳健的做法是先做差分再对差分结果做形态学过滤。4.2 差分模板法与形态学过滤用同一批正常瓶口图像生成平均模板再把待检图与模板做绝对差分这是OpenCV图像处理里最常见也最容易解释的方案。模板可以按瓶口角度制版取10张合格品的展开图逐像素平均既能压掉光源抖动也能保留稳定特征。def detect_defect(unwrapped, template): # 转成和模板相同尺寸防止缩放误差 unwrapped cv2.resize(unwrapped, (template.shape[1], template.shape[0])) # 绝对差分像素差异越大越可能是缺陷 diff cv2.absdiff(unwrapped, template) # 先做高斯平滑让差异区域连成片避免单点噪点被放大 diff cv2.GaussianBlur(diff, (3, 3), 0) # 固定阈值或Otsu自适应阈值 _, binary cv2.threshold(diff, 30, 255, cv2.THRESH_BINARY) # 闭运算把相邻的碎块连通消除空洞 kernel cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5)) closed cv2.morphologyEx(binary, cv2.MORPH_CLOSE, kernel) return closed阈值30是经验起点不建议一上来就用Otsu如果模板本身有小噪声Otsu会把噪声也分成缺陷。先固定阈值跑一批数据集统计误检和漏检再决定要不要自适应。闭运算的核大小影响连通效果核太小两段分开的裂纹会被当成两个缺陷核太大会把两个独立缺陷连成一个。瓶口展开图里宽度5像素的椭圆核基本够用。差分后还有一个需要处理的细节瓶口的凹槽、螺纹这些正常特征在不同瓶子的图像里位置会有微小偏移直接做差分会把这些偏移当成缺陷。解决方法是先对展开图做一次轻微的高斯配准也就是让模板和待检图的纵向梯度对齐或者在差分前用相位相关做亚像素平移。这是传统视觉方法里最容易被忽略的地方也是误报率高的主要原因之一。如果嫌配准复杂可以把差分换成“与模板对比局部梯度方向”梯度方向一致的就是正常结构梯度方向突变才是缺陷。计算量稍大但对轻微错位不敏感。4.3 连通域分析与缺陷分类差分得到二值图后用连通域分析把缺陷挑出来并按特征分类。def classify_defects(binary, area_floor10): num_labels, labels, stats, _ cv2.connectedComponentsWithStats(binary) defects [] for i in range(1, num_labels): area stats[i, cv2.CC_STAT_AREA] if area area_floor: continue w stats[i, cv2.CC_STAT_WIDTH] h stats[i, cv2.CC_STAT_HEIGHT] aspect max(w, h) / (min(w, h) 1e-6) if aspect 3 and area 100: defects.append((crack, area, aspect)) elif aspect 1.5: defects.append((burr, area, aspect)) else: defects.append((chip, area, aspect)) return defects这里用长宽比区分裂纹和块状缺陷裂纹的长宽比大于3而且面积偏小毛刺是从瓶口边缘凸出来的形状不规则缺口通常在边缘位置呈半圆形凹陷对应展开图上是一段垂直于边缘的色块。分类规则不一定要复杂关键是先把NG和OK分开再细分缺陷类型。如果现场要求报告缺陷的具体方向和位置还需要把展开图坐标反算回极坐标也就是根据行和列推回角度、径向位置以及距圆心的实际距离。area_floor设为10意味着小于10像素的孤立点会被忽略这个值要根据图像分辨率换算真实面积下一章讲怎么标定。5. 瓶口缺陷检测的调参与边界验证5.1 用数据集回放调整阈值别靠单张调参遇到“调参不准”的情况最常见原因是拿一张图反复调调到这张图完美了换一张图又崩。正确做法是准备一个至少三十张的混合数据集包含亮场、暗场、轻微旋偏、不同瓶口颜色的样本写一段批量回放脚本统计每张图的判定结果并输出阈值调高调低前后的中间图。我一般会把所有差值中间图拼成一张大图存到debug目录一眼就能看出是阈值偏低把纹理当成了缺陷还是阈值偏高漏掉了细裂纹。用cv2.waitKey逐张看太慢直接输出到磁盘更高效。5.2 用像素当量换算实际缺陷尺寸缺陷面积阈值的单位应该是“平方毫米”而不是像素。用已知直径的瓶口标定比例def calc_pixel_mm_ratio(cx, cy, radius, real_diameter_mm): # 图像中瓶口直径是 2*radius真实直径由卡尺量得 pixel_diameter 2 * radius return real_diameter_mm / pixel_diameter这个比值会随相机工作距离变化一旦动过相机就要重新标定。后续的area_floor就不是随意设的而是按客户标准换算出来的比如规定直径1.5mm以上的缺口要拦截先算像素当量再把1.5平方毫米转成像素阈值填进分类函数。这比拍脑袋调数字好得多也好向产线解释。5.3 把参数写进配置文件方便现场回滚把阈值、核大小、半径范围、面积下限全部放进config.json主程序启动时读取而不是散落在代码里。现场调试时改配置文件比直接改源码更安全也方便回滚。调试时可以把关键参数打印到调试窗口里例如预处理用的高斯核是(5, 5)、阈值是30、缺陷分类面积下限是10。最终验证跑一条命令把整个混合数据集过一遍python run_inspection.py --config config.json --input testset/ --output result.csv观察result.csv里每张图的判定结果再用几个已知NG样本做回归确认调高某一项阈值不会误伤原有样本。我在每轮调参之后都会把原始图片、参数文件和result.csv一起存一份带时间戳的目录产线复盘时只用一条命令就能回到当时的判定状态。本文还有配套的精品资源点击获取