
简介这是一套面向毕业设计的Mini LED芯片传统缺陷检测项目基于C、OpenCV与Qt实现适合机器视觉方向的学生参考或直接复现。方案采用标准图与待测图逐像素XOR比对定位瑕疵依据瑕疵像素数量阈值判定是否缺陷并借助Qt5完成了可视化操作界面。压缩包共84个文件包含C源码与头文件cpp/h、Qt界面设计文件ui、可执行程序及运行所需DLL依赖、BMP测试图等整体大小约174.74MB。目前已有499人学习下载内容结构完整既涵盖核心算法实现也提供可直接运行的演示环境能帮助初学者理解传统视觉检测的基本流程与图像差异比对思路同时可作为毕业设计、课程设计或期末大作业的完整备选素材。1. 一套像素级XOR比对方案足够撑起Mini LED的毕业设计吗Mini LED芯片在封装前的电极、焊盘和钝化层表面经常出现缺角、划伤、残铜、异色点这类传统缺陷。做AOI的厂商会优先上深度学习但真正落到产线首检和小批量抽检时传统机器视觉方法反而更好用不需要标注数据集、不需要训练显卡、单张图处理时间能压到几十毫秒。这套基于OpenCV和Qt的毕业设计核心思路非常直接把标准图像和待测图像做逐像素XOR运算差异区域的像素点数超过阈值就判定为缺陷。它不依赖复杂特征工程难点反而在图像对齐、阈值标定和界面集成上。适合正在做机器视觉方向课程设计、毕业设计的同学也适合想快速搭一个视觉检测Demo的工程师——用C写底层图像处理、用Qt做交互界面整套代码骨架可以直接改成PCB、五金件、薄膜材料的表面缺陷检测。2. XOR检测的数学基础与源码目录结构2.1 为什么是XOR而不是差分的绝对值传统缺陷检测里最朴素的做法是|标准图 - 待测图|像素值差异大的位置就是可疑区域。但差分对光照变化太敏感Mini LED表面的金属反射会让同一位置在不同光源角度下出现明暗波动差分结果里会充满伪轮廓。XOR运算是按位操作二值化后的黑白图做XOR只有两幅图在某个像素点上一个为0、另一个为255时才输出255。放在灰度图像里做逐位异或效果等价于把灰度值转成二进制后逐位比较任何一位不同都会让结果非零。这个特性决定了XOR方案对图像配准精度要求极高。两幅图只要有亚像素级偏移边缘位置就会产生双边缘的XOR响应缺陷区域会在一圈真实边缘附近被放大。所以实际项目里标准图和待测图必须经过相同的ROI裁剪和几何校正最好直接固定相机、固定工装让载具重复定位精度在±1像素以内。2.2 源码目录里每个文件负责什么拿到压缩包后先看根目录下这些关键文件能快速理解架构分层文件/目录职责关联模块src/check.cpp核心检测逻辑XOR操作与像素统计算法层src/coordinate.cppROI坐标管理记录标准图与待测图的裁切位置算法层src/widget.cpp主窗口业务逻辑按钮响应、文件选择、结果显示界面层src/dialog.cpp参数设置对话框阈值、形态学核大小等界面层src/paintarea.cpp自定义绘图控件负责图像缩放与绘制显示层src/main.cpp程序入口初始化QApplication和主窗口入口headfile/各模块头文件声明类接口全局img/测试样本图t1.bmp到t11.bmp及gabs.bmp等测试资源这套分层有一个明显优点算法层不依赖任何Qt类型check.cpp里只用OpenCV的Mat和C标准库这样以后想换成MFC或者纯命令行做批量测试直接把算法层搬走就行。界面层和算法层通过savedROI、阈值这类基本类型交换数据耦合度很低。2.3 检测主流程的四个步骤整个流程可以拆成四个阶段图像输入、ROI对齐、XOR差分、统计判定。图像输入支持从img/目录读取本地bmp文件也可以替换成相机SDK回调的Mat。ROI对齐依赖coordinate.cpp里维护的矩形区域这个区域需要提前标定好通常选芯片的中心有效区避开电极引线和封装边缘。XOR差分得到的是灰度图直接统计非零像素个数或做连通域分析。最终判定规则是非零像素点数超过threshold输出NG低于阈值输出OK。注意这套方案默认标准图只有一张。实际产线上不同批次、不同色温的芯片最好按批次维护标准图库否则同一套阈值很难在不同批次的亮度差异下稳定工作。3. C与OpenCV核心检测算法的工程化实现3.1 从BMP到Mat的图像装载项目里测试图是bmp格式OpenCV用imread读进来是三通道BGR图。但XOR操作最好在单通道灰度图上做三通道逐位异或会把色彩信息也纳入差异计算而Mini LED的缺陷检测更关注亮度/几何形状变化不是颜色变化。所以第一步就是转灰度#include opencv2/opencv.hpp #include QDebug cv::Mat loadAndPreprocess(const std::string path, cv::Rect roi) { cv::Mat src cv::imread(path, cv::IMREAD_COLOR); if (src.empty()) { qWarning() Failed to load image: QString::fromStdString(path); return cv::Mat(); } cv::Mat gray; cv::cvtColor(src, gray, cv::COLOR_BGR2GRAY); cv::Mat cropped gray(roi).clone(); cv::Mat blurred; cv::GaussianBlur(cropped, blurred, cv::Size(3, 3), 0); return blurred; }这里的cv::Rect roi来自coordinate.cpp模块通常是一次性标定后序列化保存每次检测时直接读取。clone()必须做因为gray(roi)只创建了头信息数据指针仍然指向原图后续修改会波及原图。高斯滤波的核大小3x3足够滤掉传感器噪声又不会把细小划痕抹平。3.2 XOR差分与形态学去噪XOR在OpenCV里没有现成函数但有bitwise_xor操作逻辑和逐像素异或完全一致。对两张归一化后的灰度图执行异或输出图中非零区域就代表两图对应位置灰度值不完全相等的地方。cv::Mat detectDefect(const cv::Mat standard, const cv::Mat target, double defectPixelRatio, int morphSize) { CV_Assert(standard.size() target.size()); CV_Assert(standard.type() target.type()); cv::Mat diff; cv::bitwise_xor(standard, target, diff); cv::Mat binary; cv::threshold(diff, binary, 0, 255, cv::THRESH_BINARY); if (morphSize 0) { cv::Mat kernel cv::getStructuringElement(cv::MORPH_RECT, cv::Size(morphSize, morphSize)); cv::morphologyEx(binary, binary, cv::MORPH_OPEN, kernel); } int nonzero cv::countNonZero(binary); defectPixelRatio static_castdouble(nonzero) / (binary.rows * binary.cols); return binary; }执行完bitwise_xor之后diff里每个像素的值是两图该像素灰度值按位异或的结果范围在0到255之间。比如1280b10000000和1270b01111111异或结果就不是一个视觉上的小差异而是255这属于XOR的固有放大效应也是为什么必须在XOR前做好光照归一化。countNonZero统计的是差分后保留的像素个数defectPixelRatio是缺陷像素占比配合后续的面积判断使用。3.3 阈值判定与缺陷标记的实用写法判定缺陷不应只看绝对像素数因为ROI区域大小不同同样面积的缺陷在大小ROI里的占比差异很大。更稳妥的方式是同时保留绝对像素数和占比两个维度用短路的逻辑做综合判断bool isDefective(double defectRatio, int nonzeroCount, double ratioThreshold, int countThreshold) { if (nonzeroCount countThreshold defectRatio ratioThreshold) { return true; } return false; }这里countThreshold过滤掉极小杂讯——比如单个像素的随机噪声在Mini LED芯片表面很常见ratioThreshold过滤掉大面积但灰度差异极弱的轻微色差。两个条件必须同时满足否则容易出现噪声多但占比小过了countThreshold没过ratioThreshold或者轻微色差占比大但绝对像素点不够多。3.4 形态学开运算的效果对比形态学开运算是先腐蚀后膨胀作用是把孤立的噪声点消除同时保持大块缺陷区域的尺寸基本不变。在Mini LED检测场景里电极上的亚像素毛刺和氧化斑点经XOR后经常出现1到2像素的离散点开运算核大小设3x3比较稳妥。如果设成5x5细划痕缺陷可能直接被腐蚀掉。建议实际调试时把morphSize开放到参数配置面板里用滑块实时观察用肉眼判断当前核大小下微小缺陷的保留情况。提示不要对binary结果做闭运算。闭运算是先膨胀后腐蚀会把相近的缺陷点连接成片造成缺陷面积虚增影响阈值判断的准确性。4. Qt界面集成从检测算法到可视化操作4.1 Qt与OpenCV的数据类型互转opencv的cv::Mat默认的内存布局是连续的行数据而Qt的QImage有多种像素格式日常最保险的是把Mat转成QImage时显式指定格式。项目里的widget.cpp已经封装了转换函数常见写法是QImage matToQImage(const cv::Mat mat) { if (mat.type() CV_8UC1) { QImage image(mat.data, mat.cols, mat.rows, mat.step, QImage::Format_Grayscale8); return image.copy(); } else if (mat.type() CV_8UC3) { cv::Mat rgb; cv::cvtColor(mat, rgb, cv::COLOR_BGR2RGB); QImage image(rgb.data, rgb.cols, rgb.rows, rgb.step, QImage::Format_RGB888); return image.copy(); } return QImage(); }注意image.copy()不能省略。QImage的构造函数接收mat.data时只保存了指针的引用没有复制数据如果Mat在栈上析构或重新分配了内存QImage会变成悬空指针绘图时直接崩溃。在OpenCV 3.4以上版本中Mat使用引用计数但mat.data的地址可能在cvtColor等操作后被释放。copy()是性能与安全之间最平衡的选择单张1080P图拷贝一次耗时可以忽略不计。4.2 主窗口的业务流程设计widget.cpp里典型的状态机是选择标准图、选择待测图、执行检测、显示结果。对应到界面布局就是两个图像显示区左边标准图、右边待测图、一个结果输出区显示检测图和判定结果、一组参数控件阈值输入框、形态学核大小。void Widget::onDetectButtonClicked() { if (m_standardPath.isEmpty() || m_targetPath.isEmpty()) { QMessageBox::warning(this, Warning, Please select both images first.); return; } cv::Rect roi m_coordinate-getSavedROI(); cv::Mat stdImg loadAndPreprocess(m_standardPath.toStdString(), roi); cv::Mat tgtImg loadAndPreprocess(m_targetPath.toStdString(), roi); if (stdImg.empty() || tgtImg.empty()) { QMessageBox::critical(this, Error, Failed to load images.); return; } double ratio 0.0; int morphSize ui-morphSpinBox-value(); int countThresh ui-countSpinBox-value(); double ratioThresh ui-ratioDoubleSpinBox-value(); cv::Mat diffMap detectDefect(stdImg, tgtImg, ratio, morphSize); bool ng isDefective(ratio, cv::countNonZero(diffMap), ratioThresh, countThresh); ui-resultLabel-setText(ng ? NG - Defect Found : OK - Pass); ui-diffView-setPixmap(QPixmap::fromImage(matToQImage(diffMap)) .scaled(ui-diffView-size(), Qt::KeepAspectRatio)); }这段逻辑里两个关键细节一是QMessageBox直接拦截异常输入避免了空路径导致的OpenCV崩溃二是m_coordinate-getSavedROI()返回值来自配置文件实际项目中可以通过鼠标框选的方式在界面上实时更新ROI。scaled做缩放时用Qt::KeepAspectRatio防止图像变形影响观察。4.3 paintarea.cpp的自定义绘图实现paintarea.cpp设计成可复用的图像控件重写paintEvent来绘制图像并且支持鼠标缩放和平移。这比直接用QLabel做显示体验好得多因为缺陷检测经常需要放大看细节。void PaintArea::paintEvent(QPaintEvent*) { QPainter painter(this); painter.setRenderHint(QPainter::Antialiasing, false); painter.setRenderHint(QPainter::SmoothPixmapTransform, true); if (!m_pixmap.isNull()) { QRect targetRect calculateTargetRect(); painter.drawPixmap(targetRect, m_pixmap); if (m_showGrid) { drawInspectionGrid(painter, targetRect); } } painter.setPen(QPen(Qt::red, 2)); painter.drawRect(m_roi); }calculateTargetRect计算当前缩放比例下的绘制目标矩形m_roi是叠加在图像上的ROI框用于可视化确认检测区域是否对准。这个自定义控件的好处是检测缺陷后不用每次都把整幅diff图交给主界面直接在PaintArea内部维护当前显示图、ROI和网格线性能开销非常小。4.4 Qt国际化的落地细节压缩包里的translations目录包含一批qt_*.qm文件说明项目预设了多语言支持。使用QTranslator在Qt 5中加载QM文件的方式是void switchLanguage(const QString langCode) { static QTranslator translator; qApp-removeTranslator(translator); if (translator.load(QString(:/translations/qt_%1.qm).arg(langCode))) { qApp-installTranslator(translator); ui-retranslateUi(this); } }这里值得关注的是QTranslator、removeTranslator与installTranslator的前后关系。先remove再load可以防止旧的翻译文件残留。如果切换语言时界面上的字符串没有更新优先检查retranslateUi是否被调用以及qm文件是否打进了qrc资源系统。很多Qt程序出现切语言没反应都是这两个原因导致的。4.5 完整依赖项排查为什么在别的电脑上跑不起来压缩包里的DLL清单暴露了典型的Qt 5 MinGW部署模式。Qt5Core.dll、Qt5Gui.dll、Qt5Widgets.dll是必需的三件套platforms/qwindows.dll必须和exe保持相对路径关系否则程序直接报could not find or load the Qt platform plugin windows。剩下的libgcc_s_dw2-1.dll、libstdc-6.dll、libwinpthread-1.dll是MinGW的运行时依赖D3Dcompiler_47.dll和opengl32sw.dll是Qt 5在部分硬件上做软件渲染的后备方案。DLL/文件缺失时现象解决方案platforms/qwindows.dll程序启动崩溃报platform plugin错误确认exe的platforms目录存在且路径相对正确libgcc_s_dw2-1.dll启动时提示找不到libgcc将MinGW的bin目录加入PATH或拷贝到exe目录libEGL.dll/libGLESV2.dll某些Win7/精简系统上界面花屏保留这两个DLL即可Qt 5会自动选择渲染后端opengl32sw.dll无独显机器上窗口显示黑屏完整拷贝到exe根目录提示部署Qt程序时用windeployqt.exe自动拷贝依赖是最省事的方式。手动拷贝容易漏掉iconengines、imageformats等插件目录导致Qt无法加载特定图片格式。5. 阈值标定方法论从视觉判据到分类决策5.1 多图采样的缺陷判据标定流程项目里gabs.bmp和tmp3.png等文件是典型的测试样本用来帮助标定阈值。标定的核心思路是用合格品和已知缺陷品各10张以上跑完XOR后分别统计缺陷像素数的分布然后取两者之间最大的gap位置作为阈值。# 批量测试脚本思路用shell循环执行main.exe并输出统计结果 for i in $(seq 1 20); do ./main.exe --standard ./img/t1.bmp --target ./img/t${i}.bmp --output result_${i}.txt done找到result_*.txt里的nonzero像素数做一个简单的频率分布表观察合格品和缺陷品的分布区间。如果两类产品的数值区间有重叠说明所选ROI或预处理参数不恰当需要回头调整形态学核大小或者ROI范围不要让阈值强行适配现有数据。5.2 像素点计数与连通域面积的取舍当前项目的判定依据是countNonZero的全局像素点数量这个指标实现最简单、执行最快但有一个明显缺点100个随机分布的噪声点和100个聚集在一起的缺角在数值上完全等价。工业场景里这两种情况的性质完全不同随机分布的噪声点更可能是环境光干扰而聚集的像素点才是指向物理缺陷的信号。改进方法是把countNonZero换成连通域分析std::vectorstd::vectorcv::Point contours; cv::findContours(binary, contours, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_SIMPLE); double maxArea 0; cv::Rect maxRect; for (const auto contour : contours) { double area cv::contourArea(contour); if (area maxArea) { maxArea area; maxRect cv::boundingRect(contour); } } bool ng maxArea maxAreaThreshold;这样既过滤了散落的噪声点又能定位缺陷的具体位置。工业落地时建议在界面里增加最大缺陷面积和缺陷总数两个参数让检验员根据历史不良品数据标定。5.3 误检来源分析与排除XOR方案最常见的误检来自三个方面边缘残影、光照不均、图像对齐偏差。边缘残影通常出现在芯片电极的轮廓上因为反光强度随角度变化明显标准图和待测图在边缘处的灰度值不一致。排除方法是在ROI选择时避开芯片边缘只在芯片平坦区域做检测。光照不均属于整体明暗波动可以在XOR之前先做直方图匹配cv::Mat normalizeIllumination(const cv::Mat target, const cv::Mat reference) { cv::Mat targetHist, refHist; int histSize 256; float range[] {0, 256}; const float* histRange {range}; cv::calcHist(target, 1, 0, cv::Mat(), targetHist, 1, histSize, histRange); cv::calcHist(reference, 1, 0, cv::Mat(), refHist, 1, histSize, histRange); cv::normalize(targetHist, targetHist, 0, 1, cv::NORM_MINMAX); cv::normalize(refHist, refHist, 0, 1, cv::NORM_MINMAX); return target; }更实用的是用cv::matchTemplate做归一化后做亮度校正或者直接用同轴光源加暗箱来消除环境光变化。6. 产线级部署的最后一公里扣准Mini LED的检测节拍把毕业设计变成能用的在线检测程序最关键的优化是处理耗时。当前方案的耗时集中在GaussianBlur、bitwise_xor和countNonZero三步。1080P灰度图上这三步在i5处理器上总计约20到30毫秒但如果支持连续检测需要把这个时间压进10毫秒以内才能匹配Mini LED产线每秒5到10颗的节拍。第一个技巧是缩小ROI。在Mini LED芯片上真正的缺陷通常出现在电极、焊盘和表面镀层区域占比不到整个视场的四分之一。把ROI从1920x1080缩小到640x480XOR和统计的耗时直接降低约70%。OpenCV对ROI操作有一层浅拷贝机制只要类型和大小一致bitwise_xor不会因为ROI减少而变得低效。第二个技巧是灰度值降采样后再做XOR。把8位灰度图降采样到4位即0到15的量化区间逐位XOR的差异会被压缩很多噪声在量化过程中自然消失。但这一步会同时削弱真实缺陷的响应尤其是亮度对比度本来就低的划痕。建议只在初筛阶段用NG品再进高分辨率复检通道形成两层检测。第三个技巧针对XOR对像素偏移敏感的问题在预处理阶段用cv::phaseCorrelate做亚像素配准。这个函数基于傅里叶变换可以计算出两幅图之间的平移量再把待测图按偏移量重映射到标准图坐标系下。测试样例里gabs_t.bmp和dilate_v_rc.bmp这类图就是用来验证配准效果的cv::phaseCorrelate只用两行核心代码cv::Point2d shift cv::phaseCorrelate(cv::Mat_float(standard), cv::Mat_float(target)); cv::Mat aligned; cv::warpAffine(target, aligned, (cv::Mat_double(2, 3) 1, 0, shift.x, 0, 1, shift.y), target.size());用phaseCorrelate配准后XOR双边缘残影能下降一个数量级这时候原来的固定阈值不用大改误检率就能明显下降。注意phaseCorrelate要求输入是浮点单通道图并且最好先做窗函数处理避免边缘效应。这套方案在当前样本上的检测能力边界是能稳定检出面积大于0.01平方毫米的缺角和划痕对亚毫米级针孔需要配合更高分辨率相机和更小的形态学核来扩展边界。本文还有配套的精品资源点击获取