简介:这是一套面向计算机视觉初学者与工业质检方向开发者的主板质量检测系统源码,基于Python与图像识别技术实现,可用于学习缺陷检测、目标检测与关键点识别等典型任务的工程落地。资源包共41个文件,以34个Python脚本为核心,涵盖推理、预处理、后处理与可视化等模块,另含2个ui界面文件、2个md说明文档、1个yml配置文件及备份与依赖清单,整体约102KB,结构紧凑便于快速阅读与二次开发。内容涉及检测、关键点、多目标跟踪等推理流程,并集成SAHI、OpenCV、深度学习工具与可视化界面,适合作为课程设计、毕业设计或质检项目原型参考。目前已有447人学习下载,可帮助读者理解从模型推理到界面展示的完整链路,掌握图像识别在主板检测场景中的实现思路与排错方法。
1. 从一张主板照片到判定结果:这套 Python 图像识别质检系统到底在做什么
产线上刚下线的工控主板,肉眼盯着看焊点、电容极性、排针歪斜,一天两千片,人眼根本扛不住。基于 Python 和图像识别的主板质量检测系统,解决的就是这件事:用工业相机拍下主板,交给算法判断有没有缺件、错件、偏移、极性反、焊锡不良这几类典型缺陷。它适合两类人——一类是想把手工目检换成自动判定的产线工程师,另一类是手里有 Python 基础、想找一个能跑通的图像识别落地项目练手的开发者。整套系统的骨架其实不复杂:图像采集、预处理、特征或模板比对、缺陷判定、结果输出,五步走完。真正拉开差距的地方在于,你怎么让算法在光照波动、板子轻微位移、不同批次色差的情况下,依然稳定地给出判定,而不是今天准明天崩。这也是后面几章要重点拆的部分。
2. 主板质检的图像识别方案选型:模板匹配、特征工程还是深度学习
2.1 三种主流路线在主板场景下的真实差异
主板质检和一般物体识别不一样,它的核心诉求是「比对」而不是「分类」。同一块板子,良品和不良品的差异可能只有几个像素——一个 0402 封装的电阻偏了 0.3mm,或者一个电容的极性标记转了一百八十度。这种场景下,三条技术路线的表现差异很大。
模板匹配是最容易上手的路线。你拿一张确认过的良品图当基准,新板子拍完对齐后逐区域做像素级或归一化互相关比对,差异超过阈值就报警。它的优点是零训练成本、可解释性强、误判了你能直接看到是哪个区域对不上。缺点是它对光照和位移极其敏感,板子稍微转一点角度,匹配分数就断崖式下跌。所以用这条路,你必须先把图像对齐做到位。
特征工程路线是在模板匹配基础上往前走一步。你不直接比像素,而是提取每个元件区域的几何特征——面积、周长、最小外接矩形长宽比、Hu 矩、颜色直方图——然后对这些特征做判定。比如一个贴片电阻的正常面积范围是 800 到 1200 像素,如果检测出来只有 300,那大概率是缺件;如果面积正常但长宽比从 2:1 变成了 1:1,那可能是偏移或者立碑。这条路对光照的鲁棒性比纯模板匹配好,但特征阈值需要针对每种元件单独调,元件种类多了之后维护成本不低。
深度学习路线是近两年越来越多人在主板质检上尝试的方向。常见做法是用目标检测模型(比如 YOLO 系列)先定位每个元件,再用一个分类网络判断该元件是否正常,或者直接用分割网络把焊点区域抠出来做像素级分类。它的优势是泛化能力强,对光照和轻微形变不敏感,缺点是标注数据成本高、训练需要 GPU、模型更新后需要重新验证。对于主板这种元件种类相对固定、缺陷模式可枚举的场景,我的经验是:如果元件种类在二十种以内、每天检测量不超过五千片,模板匹配加特征工程的组合完全够用;如果元件种类多、批次切换频繁、缺陷样本积累得快,那深度学习的投入才划得来。
2.2 用 OpenCV 搭一个最小可跑的模板比对质检流程
下面这段代码是一个最小可跑的质检流程,用 OpenCV 做图像对齐和模板比对。它假设你已经有一张良品基准图,并且相机位置固定。
import cv2 import numpy as np def align_images(good_img, test_img): """用 ORB 特征点做图像对齐,返回对齐后的测试图""" orb = cv2.ORB_create(5000) kp1, des1 = orb.detectAndCompute(good_img, None) kp2, des2 = orb.detectAndCompute(test_img, None) # 用 BFMatcher 做暴力匹配,交叉验证筛选 bf = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=True) matches = bf.match(des1, des2) matches = sorted(matches, key=lambda x: x.distance) # 取前 15% 的匹配点算单应矩阵 num_good = max(10, int(len(matches) * 0.15)) src_pts = np.float32([kp1[m.queryIdx].pt for m in matches[:num_good]]).reshape(-1, 1, 2) dst_pts = np.float32([kp2[m.trainIdx].pt for m in matches[:num_good]]).reshape(-1, 1, 2) H, mask = cv2.findHomography(dst_pts, src_pts, cv2.RANSAC, 5.0) h, w = good_img.shape[:2] aligned = cv2.warpPerspective(test_img, H, (w, h)) return aligned def detect_defects(good_img, aligned_img, diff_threshold=30, area_threshold=50): """逐像素比对,返回缺陷区域列表""" # 转灰度后做绝对差 gray_good = cv2.cvtColor(good_img, cv2.COLOR_BGR2GRAY) gray_test = cv2.cvtColor(aligned_img, cv2.COLOR_BGR2GRAY) # 高斯模糊降噪,避免单像素噪声触发误报 gray_good = cv2.GaussianBlur(gray_good, (5, 5), 0) gray_test = cv2.GaussianBlur(gray_test, (5, 5), 0) diff = cv2.absdiff(gray_good, gray_test) _, thresh = cv2.threshold(diff, diff_threshold, 255, cv2.THRESH_BINARY) # 形态学开运算去掉小噪点 kernel = np.ones((3, 3), np.uint8) thresh = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel, iterations=1) contours, _ = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) defects = [] for cnt in contours: area = cv2.contourArea(cnt) if area > area_threshold: x, y, w, h = cv2.boundingRect(cnt) defects.append({"bbox": (x, y, w, h), "area": area}) return defects # 使用示例 good = cv2.imread("golden_board.jpg") test = cv2.imread("test_board.jpg") aligned = align_images(good, test) defects = detect_defects(good, aligned) print(f"检测到 {len(defects)} 处疑似缺陷") for d in defects: print(f" 位置: {d['bbox']}, 面积: {d['area']}")这段代码的逻辑分两层。第一层是对齐:用 ORB 提取特征点,暴力匹配后取距离最小的前 15% 作为可靠匹配对,再用 RANSAC 算单应矩阵,把测试图 warp 到和良品图同一个坐标系下。这一步是整个流程的命门,对齐不准后面全白搭。第二层是比对:灰度化、高斯模糊降噪、绝对差、阈值二值化、形态学去噪、找轮廓、按面积过滤。diff_threshold控制像素差异的敏感度,设太小会把光照波动当缺陷,设太大又漏掉真实缺陷,一般从 25 到 40 之间试。area_threshold控制最小缺陷面积,低于这个值的轮廓直接丢弃,用来过滤灰尘和噪点。
2.3 参数怎么调:三个必须动手试的阈值
上面代码里有三个参数直接决定误报率和漏报率,必须拿实际产线的图去试。
diff_threshold是最关键的。我的做法是:先拿二十张确认良品跑一遍,记录每张的最大差异值,取这些值的均值加两倍标准差作为初始阈值。然后拿十张确认不良品跑一遍,看这个阈值能不能全部检出。如果漏检,往下调;如果良品误报,往上调。这个过程通常要迭代三到四轮。
area_threshold取决于你的最小缺陷尺寸要求。如果产线要求能检出 0.5mm 的偏移,而你的相机分辨率是每毫米 20 像素,那 0.5mm 对应大约 100 像素的面积,阈值就设 80 到 100 之间留点余量。
num_good(匹配点数量)影响对齐精度。取太少,单应矩阵算不准;取太多,可能引入错误匹配。15% 是一个经验值,但如果你的板子纹理特别少(比如大面积空白 PCB),可能需要降到 10% 甚至更低,同时把 ORB 的特征点数量从 5000 提到 10000。
3. 把质检流程工程化:从单张测试到批量产线部署
3.1 目录结构与批量处理脚本
单张图跑通只是第一步,产线上你需要批量处理、记录结果、标记缺陷位置。下面是一个可以直接用的批量处理脚本,假设你的图片按批次放在data/目录下,良品基准图放在golden/目录。
import os import cv2 import csv import json from datetime import datetime def batch_inspect(golden_path, test_dir, output_dir, diff_threshold=30, area_threshold=50): """批量质检主函数""" golden = cv2.imread(golden_path) if golden is None: raise FileNotFoundError(f"良品基准图读取失败: {golden_path}") os.makedirs(output_dir, exist_ok=True) results = [] # 支持的图片格式 exts = ('.jpg', '.jpeg', '.png', '.bmp') test_files = [f for f in os.listdir(test_dir) if f.lower().endswith(exts)] for fname in sorted(test_files): fpath = os.path.join(test_dir, fname) test_img = cv2.imread(fpath) if test_img is None: results.append({"file": fname, "status": "READ_ERROR", "defects": 0}) continue try: aligned = align_images(golden, test_img) defects = detect_defects(golden, aligned, diff_threshold, area_threshold) # 在图上画出缺陷框并保存 vis = aligned.copy() for d in defects: x, y, w, h = d["bbox"] cv2.rectangle(vis, (x, y), (x+w, y+h), (0, 0, 255), 2) cv2.imwrite(os.path.join(output_dir, f"marked_{fname}"), vis) status = "NG" if defects else "OK" results.append({ "file": fname, "status": status, "defects": len(defects), "detail": json.dumps(defects, ensure_ascii=False) }) except Exception as e: results.append({"file": fname, "status": "PROCESS_ERROR", "defects": -1, "detail": str(e)}) # 写 CSV 报告 report_path = os.path.join(output_dir, f"report_{datetime.now():%Y%m%d_%H%M%S}.csv") with open(report_path, 'w', newline='', encoding='utf-8') as f: writer = csv.DictWriter(f, fieldnames=["file", "status", "defects", "detail"]) writer.writeheader() writer.writerows(results) ok_count = sum(1 for r in results if r["status"] == "OK") ng_count = sum(1 for r in results if r["status"] == "NG") print(f"处理完成: 共 {len(results)} 张, OK {ok_count}, NG {ng_count}") return report_path # 调用 batch_inspect("golden/golden_board.jpg", "data/batch_001/", "output/batch_001/")这个脚本做了几件事:遍历测试目录下所有图片、逐张对齐和比对、把缺陷框画在原图上保存、把结果写进带时间戳的 CSV。status字段有三个值——OK 表示无缺陷,NG 表示检出缺陷,PROCESS_ERROR 表示处理过程中出了异常(比如图片损坏、对齐失败)。CSV 里的detail字段存了每个缺陷的坐标和面积,方便后续追溯。
3.2 用配置文件管理多产品线参数
产线不会只跑一种板子。不同型号的主板,元件布局不同、光照条件不同、缺陷判定标准也不同。把参数硬编码在脚本里,换线就得改代码,这是工程化的大忌。正确做法是把每种板子的参数抽到配置文件里。
import yaml # config.yaml 示例内容 # boards: # board_a: # golden: "golden/board_a_golden.jpg" # diff_threshold: 30 # area_threshold: 50 # roi: [100, 100, 800, 600] # 只检测这个区域 # board_b: # golden: "golden/board_b_golden.jpg" # diff_threshold: 35 # area_threshold: 80 # roi: null # 全图检测 def load_config(config_path): with open(config_path, 'r', encoding='utf-8') as f: return yaml.safe_load(f) def inspect_with_config(board_name, test_dir, config_path="config.yaml"): cfg = load_config(config_path) board_cfg = cfg["boards"][board_name] golden = cv2.imread(board_cfg["golden"]) # 如果配置了 ROI,只裁剪该区域做比对 if board_cfg.get("roi"): x, y, w, h = board_cfg["roi"] golden = golden[y:y+h, x:x+w] # 后续流程同上,参数从 board_cfg 读取 # ...用 YAML 管配置的好处是:换线时只改配置文件,不动代码;参数可以版本化管理,出问题能回溯;不同工程师维护不同板子的参数,互不干扰。roi字段特别有用——主板上有些区域本来就有合理差异(比如手焊的连接器、丝印批次不同),把这些区域排除掉能大幅降低误报。
3.3 结果可视化与人工复核界面
全自动判定在产线上永远需要人工复核兜底。我的做法是做一个简单的复核界面,把 NG 图片和对应的良品基准图并排显示,缺陷区域用红框标出,复核人员点「确认缺陷」或「误报」两个按钮,结果写回数据库。这个界面用 PyQt 或 Tkinter 都能快速搭出来,核心逻辑就是把marked_开头的图片加载出来,加两个按钮绑定事件。
import tkinter as tk from PIL import Image, ImageTk import os class ReviewApp: def __init__(self, output_dir): self.root = tk.Tk() self.root.title("质检复核") self.output_dir = output_dir self.files = [f for f in os.listdir(output_dir) if f.startswith("marked_")] self.idx = 0 self.label = tk.Label(self.root) self.label.pack() btn_frame = tk.Frame(self.root) btn_frame.pack() tk.Button(btn_frame, text="确认缺陷", command=lambda: self.judge(True)).pack(side=tk.LEFT) tk.Button(btn_frame, text="误报", command=lambda: self.judge(False)).pack(side=tk.LEFT) self.show_current() self.root.mainloop() def show_current(self): if self.idx >= len(self.files): self.label.config(text="复核完成") return img = Image.open(os.path.join(self.output_dir, self.files[self.idx])) img = img.resize((800, 600)) self.photo = ImageTk.PhotoImage(img) self.label.config(image=self.photo) def judge(self, is_defect): # 把复核结果写回 CSV 或数据库 print(f"{self.files[self.idx]}: {'确认' if is_defect else '误报'}") self.idx += 1 self.show_current() # ReviewApp("output/batch_001/")这个界面很粗糙,但够用。关键是它把「算法判定」和「人工确认」串起来了,复核结果还能反过来用来调阈值——如果某个区域连续被标为误报,就把那个区域加进 ROI 排除列表。
4. 主板质检避坑记录:五个让误报率翻倍的常见问题
4.1 光照变化导致整板误报
现象:同一块良品板,上午拍的和下午拍的比对,整板都是红色缺陷框。原因:自然光或车间照明随时间变化,相机自动曝光跟着变,导致两张图整体亮度不同。解决:固定光源和曝光参数,相机用锁定曝光模式;如果做不到,在比对前加一步直方图均衡化或自适应亮度归一化,把两张图的均值方差拉到同一水平。
4.2 板子放置角度偏差导致对齐失败
现象:对齐后的图明显歪了,缺陷框全在板子边缘。原因:ORB 特征点匹配在板子旋转角度超过 15 度时容易匹配错误,单应矩阵算出来是错的。解决:在工装上加机械定位销,把板子放置角度控制在 5 度以内;如果做不到,先用轮廓检测找到板子外框,做粗对齐,再用 ORB 做精对齐。
4.3 元件批次色差触发误报
现象:换了新批次的电容,颜色比上一批深一点,整个电容区域被标为缺陷。原因:模板比对是像素级的,颜色差异直接反映为像素差。解决:对颜色敏感的区域,改用灰度比对或者只比对梯度特征;或者在 ROI 配置里把这类元件区域排除,单独用特征工程判定。
4.4 阈值设太紧导致漏检微小缺陷
现象:产线反馈有偏移的电阻没被检出。原因:area_threshold设太大,偏移产生的差异区域面积小于阈值被过滤掉了。解决:把area_threshold降到能覆盖最小缺陷的面积,同时把diff_threshold适当调低,用形态学操作控制噪点。这两个参数要联动调,不能单独改一个。
4.5 图片读取失败导致整批中断
现象:批量处理跑到一半报错退出,后面的图全没处理。原因:某张图片损坏或格式不支持,cv2.imread返回 None,后续操作抛异常。解决:在循环里对每张图做 try-except,读取失败就记录错误状态继续跑,不要让单张图的异常中断整批。上面批量脚本里的PROCESS_ERROR状态就是干这个的。
5. 让质检系统越跑越准:用复核数据反哺阈值和 ROI
系统上线只是开始,真正决定它能不能长期用的是「越跑越准」的能力。我一般会做两件事。
第一件是定期统计误报和漏报的分布。每周把复核结果拉出来,按缺陷区域坐标做聚类,看看误报集中在哪些位置。如果某个区域连续三周都有误报,那基本可以确定是合理差异区域,直接加进 ROI 排除列表。如果某个区域有漏报,就单独把那个区域的diff_threshold调低,做区域化阈值。
第二件是积累缺陷样本,为后续升级到深度学习做准备。每次复核确认的缺陷图,按缺陷类型分类存好——缺件、偏移、极性反、焊锡不良各一个文件夹。积累到每类两百张以上,就可以训练一个轻量分类网络来替代或补充模板比对。迁移到深度学习的路径是:先用模板比对跑三个月积累标注数据,再用这些数据训练模型,最后把模型和模板比对做融合判定——两者都判 NG 才报 NG,降低误报。
# 按缺陷类型归档复核确认的样本 import shutil import os def archive_defect(img_path, defect_type, archive_root="defect_samples"): """把确认的缺陷图按类型归档""" valid_types = ["missing", "offset", "polarity", "solder"] if defect_type not in valid_types: raise ValueError(f"未知缺陷类型: {defect_type}") target_dir = os.path.join(archive_root, defect_type) os.makedirs(target_dir, exist_ok=True) fname = os.path.basename(img_path) target_path = os.path.join(target_dir, fname) shutil.copy2(img_path, target_path) print(f"已归档: {target_path}") # 在复核界面里,确认缺陷时调用 # archive_defect("output/batch_001/marked_board_023.jpg", "offset")这个归档脚本很简单,但坚持做三个月,你手里就有一套带标注的主板缺陷数据集。这套数据的价值远超过系统本身——它是你后续做模型迭代、做缺陷根因分析、做产线良率改进的基础。
我自己的习惯是每周五下午花半小时过一遍这周的复核记录,把误报区域更新到 ROI 配置里,把确认的缺陷样本归档。这个习惯坚持了半年之后,系统的误报率从最初的 15% 降到了 3% 以下,产线工人从「每张都要看一眼」变成了「只看 NG 的」。质检系统不是一次调好就完事的,它需要你持续喂数据、调参数、修边界。希望帮到你。
本文还有配套的精品资源,点击获取