做工业检测项目的时间长了,你会发现手机屏幕质检是个特别典型的场景。产线上一天要过几千块屏,划伤、亮点、暗斑、崩边这些缺陷全靠人眼盯,师傅看久了眼睛吃力,漏检率还不好控。我接触过不少做视觉的同行,都在琢磨怎么把这块用算法接过来。今天聊的这个项目,就是基于YOLOv8搭的一套手机屏幕缺陷检测系统,支持图像、视频和摄像头实时检测,PyTorch框架,Python源码,整体思路和踩坑记录都能直接复用。如果你也在做表面缺陷检测、工业质检或者类似的图像识别项目,这篇内容值得认真看一遍。项目本身的边界很清楚:不是做复杂的产线级MES系统,而是把最核心的“检测能力”跑通——给一张图,能标出缺陷的位置和类别;给一段视频,能逐帧分析;接上摄像头,能近乎实时地看到检测结果。这三个场景对应的是研发验证、离线批处理和产线试点的不同需求。整套代码用PyTorch实现,模型基于YOLOv8,训练和推理链路完整,属于那种“拿到就能跑、改改就能用”的项目。1. 项目定位与核心思路拆解1.1 手机屏幕质检的典型场景与痛点手机屏幕的缺陷检测,和普通零部件的缺陷检测还不一样。屏幕本身是玻璃加显示面板的复合结构,透光、反光,缺陷种类杂。常见的有划伤、亮点、暗点、污渍、气泡、崩边、色斑等。其中划伤和崩边这类外观缺陷,靠轮廓和纹理特征能看出来;亮暗点这类显示缺陷,需要特定的打光方式才明显。这些缺陷的尺寸往往很小,有的只有几个像素,和背景对比度又很低,传统的图像处理算法做起来非常痛苦——阈值分割可能今天调好明天就失效,换一条产线又得重来。我见过很多做视觉的同学,一上来就想着用opencv做预处理加形态学操作,结果项目越做越复杂,规则越堆越多,最后维护成本完全失控。深度学习检测模型在这类场景下的优势很明显:不需要手工设计特征,只需要把不同状态的缺陷图喂给模型,让它自己学习特征表达,泛化能力也更好。而YOLO系列又是目前工业落地最成熟的检测框架之一,速度够快、精度不差、部署方案完善,用它来做手机屏幕缺陷检测,是性价比非常高的选择。1.2 为什么选YOLOv8而不是更早的检测模型很多人会问,为什么是YOLOv8,不是Faster R-CNN,也不是YOLOv5?我的答案很直接:工程落地图个省心。先说不选Faster R-CNN的原因。两阶段检测器精度确实高,但速度是硬伤。在产线上,或者说在摄像头实时检测场景下,FPS达不到要求就是没法用。手机屏幕检测属于大批量、短节拍的应用,检测一帧的耗时必须控制得住。YOLO这种一阶段方案,把分类和定位统一回归,速度直接翻几倍。再说YOLOv8相比YOLOv5的提升。v8把检测头换成了Anchor-Free结构,不再需要预设锚框,减少了一堆需要调的参数。C2f模块替换了原来的C3模块,梯度流更丰富,特征提取能力更强。训练过程也做了不少优化,比如解耦头设计,分类和回归分支分开,收敛更快、精度也更稳。实际测下来,在同样精度的前提下,v8的模型体积和推理速度都比v5有优势。当然,选YOLOv8还有一个现实原因:PyTorch生态太成熟了。ultralytics官方仓库把训练、验证、导出、部署的链路都打通了,模型文件一个.pt搞定,导出ONNX也就一行命令。对于做项目交付的人来说,没有比这更省事的了。1.3 图像、视频、摄像头三类检测的架构差异这个项目最大的特点就是检测入口多:图像、视频、摄像头。很多人一开始不理解,觉得不就是换一个数据来源嘛?实际上差得远了。图像检测是最简单的。读一张图,推理一次,出结果。核心关注点是推理速度、精度、可视化效果。视频检测就不一样了。视频本质上是一帧一帧的图片序列,但帧之间有时间连续性,处理逻辑要考虑抽帧频率、逐帧还是跳帧、是否做结果平滑。如果每帧全量推理,算力消耗大;如果跳帧太多,又会漏掉一闪而过的缺陷。这里需要根据实际场景平衡。摄像头实时检测是难度最高的。因为它是连续的视频流,而且是无限长的流,内存管理、帧缓冲、延迟控制都要考虑。摄像头检测不能像图像检测那样一次性读入再处理,也不能像视频处理那样可以离线慢慢算,它必须在一个生产-消费模型里不断循环。更关键的是延迟。产线上如果算法处理一帧要200毫秒,那屏幕上看到的画面就有明显的卡顿感,操作体验会很差。所以这个项目的代码里,三路检测并不是简单复用一个函数,而是各自有独立的处理流程,但又共享同一个推理核心。这也是我在设计时比较满意的一点:模型只有一个,接口统一,但适配不同场景的吞吐需求。2. 缺陷定义、数据准备与模型训练的关键细节2.1 屏幕缺陷的常见类型与标注建议做检测项目,第一步永远是定义清楚“到底要检测什么”。这个项目里我建议把缺陷类别控制在5到8类,不要贪多。类别太多,样本收集压力大,模型也容易混淆。实际项目里常用的类别划分是这样的:缺陷类别特征描述标注建议划伤(scratch)细长亮线,方向不规则用多边形或矩形紧贴缺陷区域,不要包含正常纹理亮点(bright_spot)局部高亮圆点矩形框略收缩,避免把光晕包含进来暗点(dark_spot)局部亮度偏低的小点标注最小外接框即可污渍(stain)片状脏污,边界模糊尽量贴近实际轮廓,不要框得太大崩边(chip)玻璃边缘缺损沿破损边缘标注,标注框可稍微外扩气泡(bubble)圆形或椭圆形透亮区域按实际形状标注,重叠气泡建议分开框这里有一个很重要的标注原则:标注框宁可稍微紧一点,也不要松。因为YOLO这类目标检测模型对目标位置的回归是基于边界框的,如果框得太松,模型学到的是“大概那块区域有缺陷”,位置精度会下降,后续做坐标统计或者固定检测位的时候误差会很大。我在项目初期就吃过这个亏,标注员为了省事,每个框都往外扩了一圈,看起来没啥大问题,但模型训练出来之后,在精度评估里AP值掉了好几个点,后来重新规范标注才拉回来。还有一个容易被忽视的点:一张图里有多个缺陷是正常的,不要嫌多就把小的过滤掉。尤其是亮点和暗点这类小目标,本身尺寸就小,样本已经很难收集了,标注的时候再漏几个,那模型基本就学不到这类特征了。数据集规模方面,我个人的经验是:每类缺陷至少要有200到500个标注实例。手机屏幕的缺陷样本不太好收集,产线上不良品本来就不多。当时项目里的做法是先把历史积累的缺陷图全部翻出来,能标注的尽量标,再配合数据增强把规模扩大。如果原始样本实在不够,可以考虑先用公开的表面缺陷数据集做预训练,然后在自己的数据上微调,效果会比直接小样本训练好很多。2.2 数据增强与样本均衡策略屏幕缺陷检测的数据增强和通用目标检测的增强逻辑不太一样。不能盲目地做随机旋转和随机裁剪,因为屏幕图像有固定的方向性,划伤横向和纵向的视觉形态是不一样的,90度旋转或者水平翻转会改变缺陷的形态语义,干扰模型学习。我在项目里实际采用的增强策略是这样的:亮度对比度调整。屏幕缺陷和光照关系很大,模拟不同打光条件下的亮度变化,帮助模型学出更鲁棒的特征。轻微模糊与噪声。模拟传感器噪声和聚焦不准的情况,增加模型对画质下降的容忍度。随机缩放与平移。模拟不同分辨率下缺陷的尺度差异,这个对小目标检测特别有用。Mosaic增强。ultralytics默认会开Mosaic,把四张图拼成一张,可以显著增加样本多样性,对提高小目标检出效果帮助很大。但不做90度旋转和上下翻转,因为屏幕的方向是固定的,反了反而不合理。样本不均衡在工业缺陷检测里几乎必现。比如划伤很常见,气泡很少见。如果训练集里划伤占了70%,气泡只占1%,模型大概率会把气泡漏检掉。应对方法有两种:一是采样时按类别比例控制,多用欠采样或过采样;二是在训练时调整类别权重,让稀有缺陷的loss占比更大。我建议两个都做,前者是数据层面的兜底,后者是训练层面的补偿。另外还有一个技巧值得尝试:把正常样本也放一些进来训练,但只用来做背景,不标注目标。这能帮助模型学会“没有缺陷的时候不要乱框”,对降低误检率很有帮助。2.3 训练参数配置与调优经验YOLOv8用ultralytics框架训练非常方便,但默认参数是给COCO这种通用数据集准备的,用在屏幕缺陷检测上需要做几个关键调整。我用的训练配置大概是这样的:参数建议值说明modelyolov8s.pt先用s版本跑通,后续按精度需求换m或limgsz640屏幕缺陷普遍较小,如果资源允许可以试1280batch16或32看显卡显存,12G显存建议batch=16epochs100到150缺陷检测任务建议多训一些,早停阈值设严一点lr00.01默认即可,如果loss震荡就降到0.005mosaic1.0前期开,最后10个epoch关掉patience20连续20轮没提升就早停optimizerAdamW比SGD收敛稳,小数据集上表现更好我第一个版本直接拿yolov8s的默认epoch跑了50轮,效果还行但不理想,有些漏检。后来把epoch拉到150轮,配合早停,模型收敛得更充分,mAP50从0.82提升到0.9以上。另外一个提升明显的操作是最后10个epoch关闭Mosaic增强,这会让模型在真实分布上做精调,精度能再涨一点。这里有个坑:如果显存不够,batch太小会导致BN层统计不稳定,loss波动明显。解决方案是适当增大batch、降低输入分辨率,或者直接用yolov8n这种小模型做实验。3. 系统实现:三路检测模块的代码拆解3.1 环境准备与项目组织PyTorch和ultralytics的环境配置不算复杂,但在项目里踩了几次坑之后,我总结了一套相对稳的组合。Python版本建议3.9或3.10,PyTorch装2.0以上,ultralytics库直接用对应版本即可。CUDA版本要匹配显卡驱动,我试过最省心的方式是先用CPU环境把代码跑通,再切换到GPU环境,这样至少能确保逻辑本身没问题。显式依赖关系大概是这样的:pip install torch>=2.0 pip install ultralytics pip install opencv-python项目文件组织上,我习惯把检测逻辑和界面逻辑分开。核心目录大致是:weights目录放训练好的权重,datasets目录放数据集,scripts目录放训练和推理脚本,utils目录放公共工具函数。这样后面做模型迭代的时候,只需要替换weights文件,不用动业务代码。3.2 图像检测:单帧推理与结果可视化图像检测是最常用的功能,主要用来做批量验证和缺陷报告。核心逻辑是加载模型、读取图片、推理、绘制结果、保存。代码框架如下:from ultralytics import YOLO import cv2 model = YOLO('weights/best.pt') img = cv2.imread('test_images/screen_01.jpg') results = model.predict(source=img, imgsz=640, conf=0.25, verbose=False) for r in results: boxes = r.boxes for box in boxes: cls_id = int(box.cls) conf = float(box.conf) x1, y1, x2, y2 = [int(v) for v in box.xyxy[0]] label = f"{model.names[cls_id]} {conf:.2f}" cv2.rectangle(img, (x1, y1), (x2, y2), (0, 0, 255), 2) cv2.putText(img, label, (x1, y1 - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 0, 255), 2) cv2.imwrite('output/screen_result.jpg', img)这段代码看起来简单,实际使用中有几个细节值得注意。conf阈值不能设得太低,我一开始设了0.1,结果误检框特别多,大片无缺陷区域被画上红框,产线工人看了直摇头。后来把conf提到0.25到0.35之间,误检明显下降,真正有缺陷的目标基本没有漏。还有一个经验:如果单张图片很大,比如手机的原始拍摄图是4000x3000像素,直接用原图推理会很慢。可以先等比缩放到1280以内再做推理,检测完再把检测框坐标映射回原图。这个处理在批量检测时能节省大量时间。3.3 视频检测:帧序列处理与结果稳定性视频检测和图像检测的区别不只是多一个循环读帧,还有帧率和稳定性的问题。视频检测的核心代码如下:from ultralytics import YOLO import cv2 model = YOLO('weights/best.pt') cap = cv2.VideoCapture('test_videos/defect_01.mp4') out = cv2.VideoWriter('output/defect_result.mp4', cv2.VideoWriter_fourcc(*'mp4v'), cap.get(cv2.CAP_PROP_FPS), (int(cap.get(cv2.CAP_PROP_WIDTH)), int(cap.get(cv2.CAP_PROP_HEIGHT)))) frame_id = 0 while cap.isOpened(): ret, frame = cap.read() if not ret: break if frame_id % 3 != 0: # 抽帧处理,间隔2帧跑一次推理 frame_id += 1 continue results = model.predict(frame, imgsz=640, conf=0.3, verbose=False) annotated = results[0].plot() out.write(annotated) frame_id += 1 cap.release() out.release()这里我要强调一下跳帧策略。我当时做视频检测时追求画面流畅,结果每帧都跑推理,一个30秒的视频跑了七八分钟,完全没有实用价值。后来改成每3帧抽1帧推理,检测速度明显改善,缺陷出现在帧间的概率也不高,因为一个缺陷在视频里通常会持续十几帧,不会一帧就消失。但是跳帧处理有一个副作用:结果框会有轻微抖动。因为模型在每帧上的预测坐标不完全一致,画面看起来有点跳。解决办法是在所有检测框上做一个轻量的均值滤波,比如记录当前帧和历史5帧的坐标,取平均之后绘制。这样画面就平稳很多。虽然理论上有极小的响应延迟,但在工程上完全可接受。3.4 摄像头实时检测:流式推理与延迟控制摄像头实时检测是这个项目里最有意思的部分,也是真正接近产线场景的一环。核心逻辑是持续从摄像头读帧,推理,显示结果,形成循环。大体代码如下:from ultralytics import YOLO import cv2 model = YOLO('weights/best.pt') cap = cv2.VideoCapture(0) # 0代表默认摄像头,产线上从RTSP地址读流 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 # 缩小后再推理,降低延迟 small = cv2.resize(frame, (640, 640)) results = model.predict(small, imgsz=640, conf=0.3, verbose=False) annotated = results[0].plot() annotated = cv2.resize(annotated, (1280, 720)) cv2.imshow('Screen Defect Detection', annotated) if cv2.waitKey(1) & 0xFF == ord('q'): break cap.release() cv2.destroyAllWindows()实际部署中,摄像头实时检测的瓶颈往往不在模型推理本身,而在视频流的读取和解码。我遇到过一种情况:摄像头本身输出30帧,但opencv的VideoCapture在Windows下读RTSP流时自带缓冲,导致画面延迟非常明显,屏幕上看到的画面比实际慢了一两秒。这在产线上是完全不能接受的,因为操作员看到的是“过去”的画面,根本没有参考意义。解决方案是用多线程或者专门做一个视频队列,把读帧和推理解耦。读帧线程不断把新帧放入队列,推理线程从队列里取帧处理,这样可以减小读帧延迟对推理的影响。还有一个trick是每次读帧后主动丢弃队列中的旧帧,保证处理的是最新画面,实时性会好很多。4. 模型加速与部署优化4.1 权重导出与推理提速训练好的.pt权重可以在Python环境里直接推理,但要做真正的项目部署,尤其到产线边缘设备上跑,效率还不够。我习惯把模型导出成ONNX格式,再用ONNX Runtime进行推理,速度通常能提升30%到50%。导出代码很简单:model = YOLO('weights/best.pt') model.export(format='onnx', imgsz=640, opset=12)导出时会提示一条警告信息,大意是部分操作符在指定opset版本里不是最优实现,一般不用管,不影响精度。导出完可以在测试集上对比一下PyTorch推理和ONNX推理的结果,正常情况下mAP差异应该在0.5%以内。如果发现ONNX推理精度明显下降,常见原因是image shape变成了动态维度,建议导出时固定shape,或者用dynamic=True导出并在推理时保持一致的输入尺寸。ONNX Runtime的推理API比直接调model.predict稍微麻烦一点,但速度优势明显:import onnxruntime as ort import numpy as np import cv2 session = ort.InferenceSession('weights/best.onnx', providers=['CUDAExecutionProvider', 'CPUExecutionProvider']) img = cv2.imread('test_images/screen_02.jpg') img = cv2.resize(img, (640, 640)) img = img[:, :, ::-1] # BGR转RGB img = img.transpose(2, 0, 1) img = np.ascontiguousarray(img) img = img.astype(np.float32) / 255.0 img = np.expand_dims(img, axis=0) inputs = {session.get_inputs()[0].name: img} outputs = session.run(None, inputs)ONNX输出的原始tensor需要做后处理,包括解码预测框、NMS过滤等,这也是很多人认为ONNX推理“麻烦”的原因。不过好处是这一步可以完全掌控后处理逻辑,针对特定缺陷类别做定制化处理,比如过滤掉面积过小的框等。4.2 精度与速度的平衡策略做检测系统,最终是要回答一个问题:多少算力换多少精度是划算的。YOLOv8提供了n、s、m、l、x五个尺寸的模型,在手机屏幕缺陷这个任务上,我建议从s级开始验证,而不是一上来就用x级。yolov8n确实很快,CPU上都能跑得动,但小模型对弱纹理和小目标的特征提取能力有限,划伤这类细长缺陷容易被漏检。我之前在验证机上跑yolov8n,mAP50只有0.78,换成yolov8s直接跳到0.85。而yolov8l提升相对有限,到0.88左右,但推理时间增加了差不多一倍。对屏幕缺陷检测来说,yolov8s是性价比很平衡的一个选择。如果算力允许,可以尝试把输入分辨率从640提升到1280。实验结果显示,对亮点、暗点和细小划伤这些小目标,分辨率提升带来的召回率提升非常明显。但也别盲目上大分辨率,显存占用和推理时间都会相应翻倍,部署端不一定扛得住。5. 踩坑实录与排查手册5.1 检出率不高的系统性排查如果你训练出来的模型对某些缺陷总检不出来,不要急着调参,先按这个顺序排查一遍。第一看数据。这类缺陷的标注实例够不够?如果只有几十个样本,模型基本学不出来。第二看标注质量。标注框是紧贴目标还是偏大偏小?如果标注得乱七八糟,再好的模型也学不好。第三看增强策略。是不是把不合理的增强开得太猛,导致训练集中缺陷形态被破坏?第四看训练配置。epoch太短、学习率不合适、或者tensorboard里loss曲线根本没收敛,都会导致效果不好。我遇到过最诡异的情况是某个类别的mAP是0,也就是模型完全没检出。查了数据才发现,训练集里这个类别的图片全部集中在某一天采集的,当天打光角度和后续所有数据都不一样,模型学到了那个特定光照下的模式,换一个光照就废了。后面在数据采集时特意让产线师傅换了不同灯光环境重新采了一批,问题才解决。这个案例说明,工业场景里“数据代表性”比“数据量”更重要。5.2 误检多的分析与对策误检多和漏检多是两个相反的痛点。误检通常表现为:明明没有缺陷,模型画了一堆框。这种情况第一嫌疑是conf阈值太低。我建议把阈值提到0.35以上看看效果。如果阈值提高后误检还是多,就要审视训练数据里是不是缺少正常的无缺陷样本。一个有效的做法是加入负样本训练。把没有缺陷的屏幕图也放进数据集,但不做任何标注。模型在这些图上只会输出背景预测,相当于学会了“没有目标时输出什么也不框”。我试过在数据集中加入15%左右的负样本,误检率下降非常明显,而且对漏检率几乎没有负面影响。还有一种误检属于“定位松动”,比如屏幕边角的亮区容易被误判为污渍。这种通常可以通过后处理规则过滤掉。比如利用手机屏幕在图像中的位置通常是固定的特点,把检测结果中在屏幕范围外的框过滤掉,或者把面积过小、置信度偏低的框丢弃。5.3 实时性不足的瓶颈定位摄像头检测卡顿、视频检测太慢,这类问题先定位瓶颈在哪一层,再去优化,不要盲目下结论。我习惯用分段计时的办法排查。先用time.time()分别统计读帧耗时、推理耗时、绘制耗时。很多时候你会惊讶地发现,模型推理只占了一小半时间,反而是opencv的imshow绘制和图像resize拖了后腿。把绘制分辨率降低、避免在每帧上做全尺寸的图形绘制,效率可以提升不少。如果推理确实慢,优先考虑换onnx推理、模型蒸馏或者换更小的模型。还有一点容易忽略:摄像头读流本身如果用的是软解,CPU占用本身就高,留给模型推理的资源就更少了。有条件的话,用支持硬解的摄像头或者直接用硬件解码管线,整个系统的实时性会有本质提升。5.4 一个容易被忽视的问题:光照与反光做手机屏幕缺陷检测,最难的不是模型,而是图像质量。屏幕是玻璃面板,对光线极其敏感。不同的打光角度、光源亮度,拍摄出的缺陷形态差异巨大。划伤在侧光下是一条白亮的线,但在正面光下可能完全看不见;亮点在水滴光斑的干扰下,可能被误判为暗点。这个问题在数据层面无解,必须从采集端入手。打光方案直接影响模型能发挥的上限。我在项目里和产线同事反复试了几种光源方案,最终选的是低角度环光加漫射光的组合,既能突出划伤和崩边,又能保证整体亮度均匀,模型表现比打光方案调整前好了不止一个档次。做工业检测项目的朋友一定要记住:算法解决不了物理世界采不到的信息,打光是检测系统里最值得花心思的地方。我个人在实际操作中的体会是,一个完整的手机屏幕缺陷检测系统,技术栈只是基础,真正有价值的反而是那些踩过坑之后攒下来的工程判断。数据是否具备代表性、打光是不是稳定、推理链路有没有被无关操作拖慢、标注规范是不是所有人都在遵守——这些问题的答案决定了系统能不能从demo走向真正的产线使用。YOLOv8和PyTorch把模型训练和推理的门槛降得很低,但把检测系统的整体表现做到让人放心,需要的是对细节的耐心打磨。这个项目的代码结构把图像、视频、摄像头三种模式的检测链路都跑通了,后面如果再扩展,可以考虑加入缺陷分类的置信度统计、按缺陷类别输出检测报告、甚至把多个摄像头部署成分布式检测节点。方向很多,先把核心系统做好,就成功了大半。
做工业检测项目的时间长了,你会发现手机屏幕质检是个特别典型的场景。产线上一天要过几千块屏,划伤、亮点、暗斑、崩边这些缺陷全靠人眼盯,师傅看久了眼睛吃力,漏检率还不好控。我接触过不少做视觉的同行,都在琢磨怎么把这块用算法接过来。今天聊的这个项目,就是基于YOLOv8搭的一套手机屏幕缺陷检测系统,支持图像、视频和摄像头实时检测,PyTorch框架,Python源码,整体思路和踩坑记录都能直接复用。如果你也在做表面缺陷检测、工业质检或者类似的图像识别项目,这篇内容值得认真看一遍。项目本身的边界很清楚:不是做复杂的产线级MES系统,而是把最核心的“检测能力”跑通——给一张图,能标出缺陷的位置和类别;给一段视频,能逐帧分析;接上摄像头,能近乎实时地看到检测结果。这三个场景对应的是研发验证、离线批处理和产线试点的不同需求。整套代码用PyTorch实现,模型基于YOLOv8,训练和推理链路完整,属于那种“拿到就能跑、改改就能用”的项目。1. 项目定位与核心思路拆解1.1 手机屏幕质检的典型场景与痛点手机屏幕的缺陷检测,和普通零部件的缺陷检测还不一样。屏幕本身是玻璃加显示面板的复合结构,透光、反光,缺陷种类杂。常见的有划伤、亮点、暗点、污渍、气泡、崩边、色斑等。其中划伤和崩边这类外观缺陷,靠轮廓和纹理特征能看出来;亮暗点这类显示缺陷,需要特定的打光方式才明显。这些缺陷的尺寸往往很小,有的只有几个像素,和背景对比度又很低,传统的图像处理算法做起来非常痛苦——阈值分割可能今天调好明天就失效,换一条产线又得重来。我见过很多做视觉的同学,一上来就想着用opencv做预处理加形态学操作,结果项目越做越复杂,规则越堆越多,最后维护成本完全失控。深度学习检测模型在这类场景下的优势很明显:不需要手工设计特征,只需要把不同状态的缺陷图喂给模型,让它自己学习特征表达,泛化能力也更好。而YOLO系列又是目前工业落地最成熟的检测框架之一,速度够快、精度不差、部署方案完善,用它来做手机屏幕缺陷检测,是性价比非常高的选择。1.2 为什么选YOLOv8而不是更早的检测模型很多人会问,为什么是YOLOv8,不是Faster R-CNN,也不是YOLOv5?我的答案很直接:工程落地图个省心。先说不选Faster R-CNN的原因。两阶段检测器精度确实高,但速度是硬伤。在产线上,或者说在摄像头实时检测场景下,FPS达不到要求就是没法用。手机屏幕检测属于大批量、短节拍的应用,检测一帧的耗时必须控制得住。YOLO这种一阶段方案,把分类和定位统一回归,速度直接翻几倍。再说YOLOv8相比YOLOv5的提升。v8把检测头换成了Anchor-Free结构,不再需要预设锚框,减少了一堆需要调的参数。C2f模块替换了原来的C3模块,梯度流更丰富,特征提取能力更强。训练过程也做了不少优化,比如解耦头设计,分类和回归分支分开,收敛更快、精度也更稳。实际测下来,在同样精度的前提下,v8的模型体积和推理速度都比v5有优势。当然,选YOLOv8还有一个现实原因:PyTorch生态太成熟了。ultralytics官方仓库把训练、验证、导出、部署的链路都打通了,模型文件一个.pt搞定,导出ONNX也就一行命令。对于做项目交付的人来说,没有比这更省事的了。1.3 图像、视频、摄像头三类检测的架构差异这个项目最大的特点就是检测入口多:图像、视频、摄像头。很多人一开始不理解,觉得不就是换一个数据来源嘛?实际上差得远了。图像检测是最简单的。读一张图,推理一次,出结果。核心关注点是推理速度、精度、可视化效果。视频检测就不一样了。视频本质上是一帧一帧的图片序列,但帧之间有时间连续性,处理逻辑要考虑抽帧频率、逐帧还是跳帧、是否做结果平滑。如果每帧全量推理,算力消耗大;如果跳帧太多,又会漏掉一闪而过的缺陷。这里需要根据实际场景平衡。摄像头实时检测是难度最高的。因为它是连续的视频流,而且是无限长的流,内存管理、帧缓冲、延迟控制都要考虑。摄像头检测不能像图像检测那样一次性读入再处理,也不能像视频处理那样可以离线慢慢算,它必须在一个生产-消费模型里不断循环。更关键的是延迟。产线上如果算法处理一帧要200毫秒,那屏幕上看到的画面就有明显的卡顿感,操作体验会很差。所以这个项目的代码里,三路检测并不是简单复用一个函数,而是各自有独立的处理流程,但又共享同一个推理核心。这也是我在设计时比较满意的一点:模型只有一个,接口统一,但适配不同场景的吞吐需求。2. 缺陷定义、数据准备与模型训练的关键细节2.1 屏幕缺陷的常见类型与标注建议做检测项目,第一步永远是定义清楚“到底要检测什么”。这个项目里我建议把缺陷类别控制在5到8类,不要贪多。类别太多,样本收集压力大,模型也容易混淆。实际项目里常用的类别划分是这样的:缺陷类别特征描述标注建议划伤(scratch)细长亮线,方向不规则用多边形或矩形紧贴缺陷区域,不要包含正常纹理亮点(bright_spot)局部高亮圆点矩形框略收缩,避免把光晕包含进来暗点(dark_spot)局部亮度偏低的小点标注最小外接框即可污渍(stain)片状脏污,边界模糊尽量贴近实际轮廓,不要框得太大崩边(chip)玻璃边缘缺损沿破损边缘标注,标注框可稍微外扩气泡(bubble)圆形或椭圆形透亮区域按实际形状标注,重叠气泡建议分开框这里有一个很重要的标注原则:标注框宁可稍微紧一点,也不要松。因为YOLO这类目标检测模型对目标位置的回归是基于边界框的,如果框得太松,模型学到的是“大概那块区域有缺陷”,位置精度会下降,后续做坐标统计或者固定检测位的时候误差会很大。我在项目初期就吃过这个亏,标注员为了省事,每个框都往外扩了一圈,看起来没啥大问题,但模型训练出来之后,在精度评估里AP值掉了好几个点,后来重新规范标注才拉回来。还有一个容易被忽视的点:一张图里有多个缺陷是正常的,不要嫌多就把小的过滤掉。尤其是亮点和暗点这类小目标,本身尺寸就小,样本已经很难收集了,标注的时候再漏几个,那模型基本就学不到这类特征了。数据集规模方面,我个人的经验是:每类缺陷至少要有200到500个标注实例。手机屏幕的缺陷样本不太好收集,产线上不良品本来就不多。当时项目里的做法是先把历史积累的缺陷图全部翻出来,能标注的尽量标,再配合数据增强把规模扩大。如果原始样本实在不够,可以考虑先用公开的表面缺陷数据集做预训练,然后在自己的数据上微调,效果会比直接小样本训练好很多。2.2 数据增强与样本均衡策略屏幕缺陷检测的数据增强和通用目标检测的增强逻辑不太一样。不能盲目地做随机旋转和随机裁剪,因为屏幕图像有固定的方向性,划伤横向和纵向的视觉形态是不一样的,90度旋转或者水平翻转会改变缺陷的形态语义,干扰模型学习。我在项目里实际采用的增强策略是这样的:亮度对比度调整。屏幕缺陷和光照关系很大,模拟不同打光条件下的亮度变化,帮助模型学出更鲁棒的特征。轻微模糊与噪声。模拟传感器噪声和聚焦不准的情况,增加模型对画质下降的容忍度。随机缩放与平移。模拟不同分辨率下缺陷的尺度差异,这个对小目标检测特别有用。Mosaic增强。ultralytics默认会开Mosaic,把四张图拼成一张,可以显著增加样本多样性,对提高小目标检出效果帮助很大。但不做90度旋转和上下翻转,因为屏幕的方向是固定的,反了反而不合理。样本不均衡在工业缺陷检测里几乎必现。比如划伤很常见,气泡很少见。如果训练集里划伤占了70%,气泡只占1%,模型大概率会把气泡漏检掉。应对方法有两种:一是采样时按类别比例控制,多用欠采样或过采样;二是在训练时调整类别权重,让稀有缺陷的loss占比更大。我建议两个都做,前者是数据层面的兜底,后者是训练层面的补偿。另外还有一个技巧值得尝试:把正常样本也放一些进来训练,但只用来做背景,不标注目标。这能帮助模型学会“没有缺陷的时候不要乱框”,对降低误检率很有帮助。2.3 训练参数配置与调优经验YOLOv8用ultralytics框架训练非常方便,但默认参数是给COCO这种通用数据集准备的,用在屏幕缺陷检测上需要做几个关键调整。我用的训练配置大概是这样的:参数建议值说明modelyolov8s.pt先用s版本跑通,后续按精度需求换m或limgsz640屏幕缺陷普遍较小,如果资源允许可以试1280batch16或32看显卡显存,12G显存建议batch=16epochs100到150缺陷检测任务建议多训一些,早停阈值设严一点lr00.01默认即可,如果loss震荡就降到0.005mosaic1.0前期开,最后10个epoch关掉patience20连续20轮没提升就早停optimizerAdamW比SGD收敛稳,小数据集上表现更好我第一个版本直接拿yolov8s的默认epoch跑了50轮,效果还行但不理想,有些漏检。后来把epoch拉到150轮,配合早停,模型收敛得更充分,mAP50从0.82提升到0.9以上。另外一个提升明显的操作是最后10个epoch关闭Mosaic增强,这会让模型在真实分布上做精调,精度能再涨一点。这里有个坑:如果显存不够,batch太小会导致BN层统计不稳定,loss波动明显。解决方案是适当增大batch、降低输入分辨率,或者直接用yolov8n这种小模型做实验。3. 系统实现:三路检测模块的代码拆解3.1 环境准备与项目组织PyTorch和ultralytics的环境配置不算复杂,但在项目里踩了几次坑之后,我总结了一套相对稳的组合。Python版本建议3.9或3.10,PyTorch装2.0以上,ultralytics库直接用对应版本即可。CUDA版本要匹配显卡驱动,我试过最省心的方式是先用CPU环境把代码跑通,再切换到GPU环境,这样至少能确保逻辑本身没问题。显式依赖关系大概是这样的:pip install torch>=2.0 pip install ultralytics pip install opencv-python项目文件组织上,我习惯把检测逻辑和界面逻辑分开。核心目录大致是:weights目录放训练好的权重,datasets目录放数据集,scripts目录放训练和推理脚本,utils目录放公共工具函数。这样后面做模型迭代的时候,只需要替换weights文件,不用动业务代码。3.2 图像检测:单帧推理与结果可视化图像检测是最常用的功能,主要用来做批量验证和缺陷报告。核心逻辑是加载模型、读取图片、推理、绘制结果、保存。代码框架如下:from ultralytics import YOLO import cv2 model = YOLO('weights/best.pt') img = cv2.imread('test_images/screen_01.jpg') results = model.predict(source=img, imgsz=640, conf=0.25, verbose=False) for r in results: boxes = r.boxes for box in boxes: cls_id = int(box.cls) conf = float(box.conf) x1, y1, x2, y2 = [int(v) for v in box.xyxy[0]] label = f"{model.names[cls_id]} {conf:.2f}" cv2.rectangle(img, (x1, y1), (x2, y2), (0, 0, 255), 2) cv2.putText(img, label, (x1, y1 - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 0, 255), 2) cv2.imwrite('output/screen_result.jpg', img)这段代码看起来简单,实际使用中有几个细节值得注意。conf阈值不能设得太低,我一开始设了0.1,结果误检框特别多,大片无缺陷区域被画上红框,产线工人看了直摇头。后来把conf提到0.25到0.35之间,误检明显下降,真正有缺陷的目标基本没有漏。还有一个经验:如果单张图片很大,比如手机的原始拍摄图是4000x3000像素,直接用原图推理会很慢。可以先等比缩放到1280以内再做推理,检测完再把检测框坐标映射回原图。这个处理在批量检测时能节省大量时间。3.3 视频检测:帧序列处理与结果稳定性视频检测和图像检测的区别不只是多一个循环读帧,还有帧率和稳定性的问题。视频检测的核心代码如下:from ultralytics import YOLO import cv2 model = YOLO('weights/best.pt') cap = cv2.VideoCapture('test_videos/defect_01.mp4') out = cv2.VideoWriter('output/defect_result.mp4', cv2.VideoWriter_fourcc(*'mp4v'), cap.get(cv2.CAP_PROP_FPS), (int(cap.get(cv2.CAP_PROP_WIDTH)), int(cap.get(cv2.CAP_PROP_HEIGHT)))) frame_id = 0 while cap.isOpened(): ret, frame = cap.read() if not ret: break if frame_id % 3 != 0: # 抽帧处理,间隔2帧跑一次推理 frame_id += 1 continue results = model.predict(frame, imgsz=640, conf=0.3, verbose=False) annotated = results[0].plot() out.write(annotated) frame_id += 1 cap.release() out.release()这里我要强调一下跳帧策略。我当时做视频检测时追求画面流畅,结果每帧都跑推理,一个30秒的视频跑了七八分钟,完全没有实用价值。后来改成每3帧抽1帧推理,检测速度明显改善,缺陷出现在帧间的概率也不高,因为一个缺陷在视频里通常会持续十几帧,不会一帧就消失。但是跳帧处理有一个副作用:结果框会有轻微抖动。因为模型在每帧上的预测坐标不完全一致,画面看起来有点跳。解决办法是在所有检测框上做一个轻量的均值滤波,比如记录当前帧和历史5帧的坐标,取平均之后绘制。这样画面就平稳很多。虽然理论上有极小的响应延迟,但在工程上完全可接受。3.4 摄像头实时检测:流式推理与延迟控制摄像头实时检测是这个项目里最有意思的部分,也是真正接近产线场景的一环。核心逻辑是持续从摄像头读帧,推理,显示结果,形成循环。大体代码如下:from ultralytics import YOLO import cv2 model = YOLO('weights/best.pt') cap = cv2.VideoCapture(0) # 0代表默认摄像头,产线上从RTSP地址读流 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 # 缩小后再推理,降低延迟 small = cv2.resize(frame, (640, 640)) results = model.predict(small, imgsz=640, conf=0.3, verbose=False) annotated = results[0].plot() annotated = cv2.resize(annotated, (1280, 720)) cv2.imshow('Screen Defect Detection', annotated) if cv2.waitKey(1) & 0xFF == ord('q'): break cap.release() cv2.destroyAllWindows()实际部署中,摄像头实时检测的瓶颈往往不在模型推理本身,而在视频流的读取和解码。我遇到过一种情况:摄像头本身输出30帧,但opencv的VideoCapture在Windows下读RTSP流时自带缓冲,导致画面延迟非常明显,屏幕上看到的画面比实际慢了一两秒。这在产线上是完全不能接受的,因为操作员看到的是“过去”的画面,根本没有参考意义。解决方案是用多线程或者专门做一个视频队列,把读帧和推理解耦。读帧线程不断把新帧放入队列,推理线程从队列里取帧处理,这样可以减小读帧延迟对推理的影响。还有一个trick是每次读帧后主动丢弃队列中的旧帧,保证处理的是最新画面,实时性会好很多。4. 模型加速与部署优化4.1 权重导出与推理提速训练好的.pt权重可以在Python环境里直接推理,但要做真正的项目部署,尤其到产线边缘设备上跑,效率还不够。我习惯把模型导出成ONNX格式,再用ONNX Runtime进行推理,速度通常能提升30%到50%。导出代码很简单:model = YOLO('weights/best.pt') model.export(format='onnx', imgsz=640, opset=12)导出时会提示一条警告信息,大意是部分操作符在指定opset版本里不是最优实现,一般不用管,不影响精度。导出完可以在测试集上对比一下PyTorch推理和ONNX推理的结果,正常情况下mAP差异应该在0.5%以内。如果发现ONNX推理精度明显下降,常见原因是image shape变成了动态维度,建议导出时固定shape,或者用dynamic=True导出并在推理时保持一致的输入尺寸。ONNX Runtime的推理API比直接调model.predict稍微麻烦一点,但速度优势明显:import onnxruntime as ort import numpy as np import cv2 session = ort.InferenceSession('weights/best.onnx', providers=['CUDAExecutionProvider', 'CPUExecutionProvider']) img = cv2.imread('test_images/screen_02.jpg') img = cv2.resize(img, (640, 640)) img = img[:, :, ::-1] # BGR转RGB img = img.transpose(2, 0, 1) img = np.ascontiguousarray(img) img = img.astype(np.float32) / 255.0 img = np.expand_dims(img, axis=0) inputs = {session.get_inputs()[0].name: img} outputs = session.run(None, inputs)ONNX输出的原始tensor需要做后处理,包括解码预测框、NMS过滤等,这也是很多人认为ONNX推理“麻烦”的原因。不过好处是这一步可以完全掌控后处理逻辑,针对特定缺陷类别做定制化处理,比如过滤掉面积过小的框等。4.2 精度与速度的平衡策略做检测系统,最终是要回答一个问题:多少算力换多少精度是划算的。YOLOv8提供了n、s、m、l、x五个尺寸的模型,在手机屏幕缺陷这个任务上,我建议从s级开始验证,而不是一上来就用x级。yolov8n确实很快,CPU上都能跑得动,但小模型对弱纹理和小目标的特征提取能力有限,划伤这类细长缺陷容易被漏检。我之前在验证机上跑yolov8n,mAP50只有0.78,换成yolov8s直接跳到0.85。而yolov8l提升相对有限,到0.88左右,但推理时间增加了差不多一倍。对屏幕缺陷检测来说,yolov8s是性价比很平衡的一个选择。如果算力允许,可以尝试把输入分辨率从640提升到1280。实验结果显示,对亮点、暗点和细小划伤这些小目标,分辨率提升带来的召回率提升非常明显。但也别盲目上大分辨率,显存占用和推理时间都会相应翻倍,部署端不一定扛得住。5. 踩坑实录与排查手册5.1 检出率不高的系统性排查如果你训练出来的模型对某些缺陷总检不出来,不要急着调参,先按这个顺序排查一遍。第一看数据。这类缺陷的标注实例够不够?如果只有几十个样本,模型基本学不出来。第二看标注质量。标注框是紧贴目标还是偏大偏小?如果标注得乱七八糟,再好的模型也学不好。第三看增强策略。是不是把不合理的增强开得太猛,导致训练集中缺陷形态被破坏?第四看训练配置。epoch太短、学习率不合适、或者tensorboard里loss曲线根本没收敛,都会导致效果不好。我遇到过最诡异的情况是某个类别的mAP是0,也就是模型完全没检出。查了数据才发现,训练集里这个类别的图片全部集中在某一天采集的,当天打光角度和后续所有数据都不一样,模型学到了那个特定光照下的模式,换一个光照就废了。后面在数据采集时特意让产线师傅换了不同灯光环境重新采了一批,问题才解决。这个案例说明,工业场景里“数据代表性”比“数据量”更重要。5.2 误检多的分析与对策误检多和漏检多是两个相反的痛点。误检通常表现为:明明没有缺陷,模型画了一堆框。这种情况第一嫌疑是conf阈值太低。我建议把阈值提到0.35以上看看效果。如果阈值提高后误检还是多,就要审视训练数据里是不是缺少正常的无缺陷样本。一个有效的做法是加入负样本训练。把没有缺陷的屏幕图也放进数据集,但不做任何标注。模型在这些图上只会输出背景预测,相当于学会了“没有目标时输出什么也不框”。我试过在数据集中加入15%左右的负样本,误检率下降非常明显,而且对漏检率几乎没有负面影响。还有一种误检属于“定位松动”,比如屏幕边角的亮区容易被误判为污渍。这种通常可以通过后处理规则过滤掉。比如利用手机屏幕在图像中的位置通常是固定的特点,把检测结果中在屏幕范围外的框过滤掉,或者把面积过小、置信度偏低的框丢弃。5.3 实时性不足的瓶颈定位摄像头检测卡顿、视频检测太慢,这类问题先定位瓶颈在哪一层,再去优化,不要盲目下结论。我习惯用分段计时的办法排查。先用time.time()分别统计读帧耗时、推理耗时、绘制耗时。很多时候你会惊讶地发现,模型推理只占了一小半时间,反而是opencv的imshow绘制和图像resize拖了后腿。把绘制分辨率降低、避免在每帧上做全尺寸的图形绘制,效率可以提升不少。如果推理确实慢,优先考虑换onnx推理、模型蒸馏或者换更小的模型。还有一点容易忽略:摄像头读流本身如果用的是软解,CPU占用本身就高,留给模型推理的资源就更少了。有条件的话,用支持硬解的摄像头或者直接用硬件解码管线,整个系统的实时性会有本质提升。5.4 一个容易被忽视的问题:光照与反光做手机屏幕缺陷检测,最难的不是模型,而是图像质量。屏幕是玻璃面板,对光线极其敏感。不同的打光角度、光源亮度,拍摄出的缺陷形态差异巨大。划伤在侧光下是一条白亮的线,但在正面光下可能完全看不见;亮点在水滴光斑的干扰下,可能被误判为暗点。这个问题在数据层面无解,必须从采集端入手。打光方案直接影响模型能发挥的上限。我在项目里和产线同事反复试了几种光源方案,最终选的是低角度环光加漫射光的组合,既能突出划伤和崩边,又能保证整体亮度均匀,模型表现比打光方案调整前好了不止一个档次。做工业检测项目的朋友一定要记住:算法解决不了物理世界采不到的信息,打光是检测系统里最值得花心思的地方。我个人在实际操作中的体会是,一个完整的手机屏幕缺陷检测系统,技术栈只是基础,真正有价值的反而是那些踩过坑之后攒下来的工程判断。数据是否具备代表性、打光是不是稳定、推理链路有没有被无关操作拖慢、标注规范是不是所有人都在遵守——这些问题的答案决定了系统能不能从demo走向真正的产线使用。YOLOv8和PyTorch把模型训练和推理的门槛降得很低,但把检测系统的整体表现做到让人放心,需要的是对细节的耐心打磨。这个项目的代码结构把图像、视频、摄像头三种模式的检测链路都跑通了,后面如果再扩展,可以考虑加入缺陷分类的置信度统计、按缺陷类别输出检测报告、甚至把多个摄像头部署成分布式检测节点。方向很多,先把核心系统做好,就成功了大半。