简介:一套基于YOLOv8的光伏电池缺陷检测项目,面向需要掌握目标检测算法落地与工业质检场景的开发者与学习者,覆盖模型训练、推理与部署全流程。项目共收录一千一百一十一个文件,其中包含一百五十九个Python训练/推理脚本、六十八个YAML模型配置、二百二十个TXT标注文件以及三百二十三个Markdown笔记,另附二百六十七张PNG结果图和多种Dockerfile,方便理解检测效果与跨平台部署;压缩包约三十一点九七兆字节,目录组织清晰。目前已有五百五十七人学习浏览。通过train.py与predict.py可复现完整训练和预测流程,利用YOLOv8的单阶段回归机制对划痕、污渍、裂纹等光伏电池缺陷进行定位与分类。资源还提供C++与Python实现参考及CPU、ARM64、JetPack等环境配置,适合在GPU环境下实战调参,也可作为课程设计或企业预研的起步模板。
1. 基于yolov8实现的光伏电池缺陷检测项目:先说清它解决什么问题
做基于yolov8实现的光伏电池缺陷检测项目,第一件事不是读论文,而是面对真实的EL测试图像。光伏产线的EL(电致发光)测试仪每班次会拍几千张电池片照片,隐裂、断栅、黑斑、碎片全都藏在灰度图里。人工复检盯着屏幕看,疲劳之后漏检率明显升高,尤其夜班时段,一条隐裂漏过去就是一块组件的事故隐患。用yolov8替换人工目检,核心诉求不是“更智能”,而是把漏检率压下来、把检测节拍提上去。yolov8在单阶段检测器里属于生态最顺手的那个:训练命令短、预训练权重全、部署链路成熟,做光伏缺陷检测这种强背景、弱纹理、小目标的场景,比通用目标检测多出不少值得较真的细节。这篇文章适合谁?手上有EL图像数据、想在企业里落地一个能用而不是只在论文里好看的检测方案的人。
2. 从EL图像到YOLO输入:光伏电池缺陷特征与数据准备要点
2.1 缺陷类别怎么定:训练前必须先看的EL图像形态
EL图像里,隐裂是一条细长的高亮折线,断栅是一整片平行的细线断裂,黑斑是暗色块,碎片是几何状亮块,脏污是边缘渐变暗区。这些形态差异决定了你该用目标检测还是实例分割、该用矩形框还是旋转框。我的经验是,第一版项目里全部用矩形框做检测,不做分割。原因很简单:产线要的是“有没有缺陷”和“缺陷在哪”,矩形框加类别已经能驱动机械臂或人工复检流程,而分割标注成本大约是检测的三倍,项目周期不允许。
但矩形框有个绕不开的问题:隐裂和断栅是长条形的,标注框会非常扁,宽高比经常超过10:1。yolov8的anchor free机制对极端宽高比并不敏感,真正敏感的是标注一致性。一张隐裂因为亮度不同被标注成两截,另一张被框成一个大框,模型会被教糊涂。所以标注规范必须在画框阶段就统一:细长缺陷全部用外接矩形框,不追边缘;缺陷断裂超过图像尺寸三分之一就分两个框,否则算一个框。这个规范写进标注文档,比事后清洗数据省力得多。
2.2 图像预处理与标注:用labelme转成yolov8能吃的格式
常见做法是用labelme标注,因为它能导JSON,后续转换灵活。但labelme默认输出的是多边形点坐标,而yolov8训练需要的是归一化的中心点加宽高txt文件。这个转换不复杂,坑却在图像尺寸和归一化上。
import json import os import glob # 类别名列表,顺序要和你后面训练用的yaml一致 CLASS_NAMES = ["crack", "broken_grid", "black_spot", "fragment"] def convert_labelme_json_to_yolo(json_path, target_dir, img_width, img_height): with open(json_path, 'r', encoding='utf-8') as f: data = json.load(f) txt_name = os.path.basename(json_path).replace('.json', '.txt') out_path = os.path.join(target_dir, txt_name) with open(out_path, 'w', encoding='utf-8') as out_f: for shape in data['shapes']: label = shape['label'] if label not in CLASS_NAMES: continue # 取多边形外接矩形,适合细长缺陷的一致性标注 xs = [p[0] for p in shape['points']] ys = [p[1] for p in shape['points']] x_min, x_max = min(xs), max(xs) y_min, y_max = min(ys), max(ys) box_w = x_max - x_min box_h = y_max - y_min # yolov8需要中心点坐标和宽高,全部归一化到0-1 center_x = (x_min + box_w / 2) / img_width center_y = (y_min + box_h / 2) / img_height norm_w = box_w / img_width norm_h = box_h / img_height class_id = CLASS_NAMES.index(label) out_f.write(f"{class_id} {center_x:.6f} {center_y:.6f} {norm_w:.6f} {norm_h:.6f}\n") print(f"converted: {json_path}") # 遍历标注目录 for json_file in glob.glob("/data/el_images/labelme/*.json"): convert_labelme_json_to_yolo(json_file, "/data/el_images/yolo_labels", 1024, 1024)这段代码把labelme的多边形转成外接矩形,直接规避了标注形状不统一的问题。参数说明里最值得关注的是img_width和img_height。EL相机输出通常是灰度图,尺寸可能在1024x1024到2048x2048之间,如果你训练时打算缩放输入,这里仍按原始图尺寸归一化,训练时yolov8会自动做letterbox。注意CLASS_NAMES的顺序必须和数据集yaml里的names完全一致,顺序错了就是经典的“训练时没问题、推理时全部对不上”的坑。
2.3 数据不平衡处理:用cutout和复制粘贴增强
光伏EL缺陷数据里,黑斑和隐裂往往占了大头,断栅和碎片可能只有几十张。yolov8自带的数据增强能缓解一部分,但对付不了类别数量相差两个数量级的情况。我一般会做两件事:一是对少数类样本做在线复制粘贴增强——在训练脚本里把含有断栅的样本多采样几遍;二是用cutout随机遮挡部分正常区域,强迫模型不要只依赖背景特征。在yolov8里,可以通过调整augment参数和设置mosaic来实现,但最直接的是给少数类别提高采样权重,在yaml数据集配置里把少数类的weight调高。注意别权重拉满,否则模型会把正常电池片误判成缺陷。
3. 用ubuntu20.04搭建yolov8训练环境并跑通第一个模型
3.1 环境搭建:CPU与GPU两套走法
在ubuntu20.04上搭建yolov8环境,CPU版本和GPU版本完全是两种节奏。CPU版本适合先跑通流程、验证数据格式对不对,但光伏EL图分辨率高,一张640x640的推理在CPU上要两三秒,训练就别想了,只适合做小规模冒烟测试。GPU版本的关键是torch和CUDA的版本匹配,这方面最容易翻车。常见做法是用conda独立环境,不污染系统Python。
# 用conda创建独立环境,避免系统Python被改坏 conda create -n yolov8 python=3.10 -y conda activate yolov8 # 安装ultralytics库,CPU版直接装 pip install ultralytics # GPU版先确认驱动支持的CUDA版本,再装对应torch # 例如驱动支持CUDA 11.8时 pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 pip install ultralytics # 验证是否装好 yolo predict model=yolov8n.pt source=/data/el_images/test.jpg参数说明:yolo predict这条命令是ultralytics的统一入口,它会自动下载yolov8n的预训练权重。第一次联网跑会慢一些,因为要下载权重,后续就快了。这里有个容易被忽略的点:source路径里如果有中文名,yolov8在部分版本上会报错找不到文件,原因是底层opencv的路径编码处理有问题,所以项目目录名和图像文件名尽量全英文。
3.2 训练命令与关键参数:从yolov8s到yolov8m的取舍
光伏电池缺陷是小目标密集场景,yolov8n作为基线检测能力偏弱,yolov8s是性价比最稳的起点。如果显存足够,可以上yolov8m,但EL图像往往有一部分大尺寸缺陷,模型容量更大的收益会被数据量限制。训练命令的写法直接决定了尾巴上的mAP能到多少。
# 从yolov8s.pt继续训练,比从零训收敛快得多 yolo train data=/data/el_images/dataset.yaml \ model=yolov8s.pt \ epochs=150 \ imgsz=1280 \ batch=8 \ patience=20 \ device=0 \ project=/data/el_runs \ name=el_defect_v1 \ seed=42 \ pretrained=True逐项说参数。imgsz=1280是我在这个项目里最强调的一个,EL图像里的隐裂只有几个像素宽,缩到640会直接丢失关键纹理,1280能让细长缺陷保有可辨识的边缘;代价是显存暴涨一倍,12G显存跑batch=8已经接近极限。batch=8在单卡12G到24G之间比较稳,batch太小会让BN层的统计量抖动,batch太大又容易在早期阶段过拟合到背景。patience=20表示验证集mAP连续20轮不涨就提前停,这个值不是随便拍的——光伏EL数据类别少、背景单一,模型在60到80轮之间大概率收敛,50轮不涨基本就是数据或标注有问题,耗着没意义。
3.3 训练过程监控:损失曲线和验证mAP怎么判断是否过拟合
训练结束会生成results.png,里面有box_loss、cls_loss、dfl_loss三条训练曲线和对应的验证曲线。我要提醒的是别只盯着mAP看,这三条loss曲线的走势才是判断过拟合的早期信号。常见情况是训练loss一直降、验证loss在第40轮开始反弹,这说明模型开始死记训练集里的光照纹理了。另一个坑:yolov8默认的输出里,标签类别有val/box_loss,如果验证集的loss波动剧烈但训练集平滑,先怀疑验证集图片和训练集有重叠,或者标注框质量太差导致验证集评估噪声大。
4. 模型评估与推理:确认缺陷类型和定位是否可用
4.1 用mAP和混淆矩阵看漏检:为什么mAP@0.5:0.95上不去
光伏电池缺陷检测里,mAP@0.5:0.95往往不好看,这不是模型不行,而是缺陷的长条形导致预测框和标注框的IoU计算很吃亏。一个长宽比15:1的隐裂,预测框中心点偏离几个像素,IoU就掉到0.3以下。所以做这个项目要调整评估心态:主指标用mAP@0.5,辅指标看每类的精确率和召回率。在yolov8的验证命令里可以单独指定IoU阈值。
# 评估时只算IoU=0.5的mAP,并输出每类的详细指标 yolo val model=/data/el_runs/el_defect_v1/weights/best.pt \ data=/data/el_images/dataset.yaml \ imgsz=1280 \ conf=0.25 \ iou=0.6 \ verbose=True参数说明:conf=0.25是置信度阈值,低于这个值的预测框会被过滤;iou=0.6是NMS的IoU阈值,越大保留的框越多。对光伏电池这个场景,conf不值得压到0.1,因为一旦误检率上来,后端的复检工位会被无效报警淹没,产线工人会直接关掉系统。iou设到0.6比较平衡,因为缺陷框之间很少重叠,不像密集人群场景需要更激进的NMS。跑完这个命令看混淆矩阵,如果broken_grid的召回率远低于其他类,大概率是标注框太少,或者这一类图像里的缺陷和背景纹理长得太像。
4.2 推理脚本与置信度阈值:在批量EL图像上跑检测
训练完模型只是开始,真正的项目要面对每天几千张图的批量推理。写一个脚本,把产线导出的EL图像逐张丢给模型,输出带框的标注图和一张汇总CSV。这个环节我吃过亏:最初用默认的conf阈值,结果夜班图像噪点多,假阳性框特别多,后来改成按缺陷类别分别设阈值才稳住。
from ultralytics import YOLO import os import glob import csv model = YOLO("/data/el_runs/el_defect_v1/weights/best.pt") # 按类别设置不同置信度:碎片容易误检,阈值调高;隐裂漏检代价大,阈值压低 class_conf = { 0: 0.2, # crack: 漏检代价高,保守一点 1: 0.35, # broken_grid: 误检多,阈值提高 2: 0.25, # black_spot 3: 0.4, # fragment: 误检多,阈值最高 } image_dir = "/data/el_images/production/" results_csv = "/data/el_results/predictions.csv" with open(results_csv, 'w', newline='') as f: writer = csv.writer(f) writer.writerow(["image", "class", "confidence", "x_center", "y_center", "width", "height"]) for img_path in glob.glob(os.path.join(image_dir, "*.png")): # 单张图推理 results = model.predict(source=img_path, imgsz=1280, conf=0.2, iou=0.6) for r in results: boxes = r.boxes for i in range(len(boxes)): cls_id = int(boxes.cls[i]) if float(boxes.conf[i]) < class_conf.get(cls_id, 0.25): continue xc, yc, w, h = boxes.xywh[i].tolist() writer.writerow([os.path.basename(img_path), cls_id, round(float(boxes.conf[i]), 4), round(xc, 2), round(yc, 2), round(w, 2), round(h, 2)]) print(f"done, results saved to {results_csv}")这段脚本里最值得说的是model.predict的conf参数设的0.2是所有类别的最低阈值,但在写入CSV前又用class_conf做了一道过滤。这么做是因为不能只设一个全局conf,不同缺陷的误检率差异很大,分开设置才能平衡漏检和误检。注意循环里boxes.xywh拿到的是像素坐标,如果后面要做缺陷大小统计分析,直接用它即可,不需要转换。
4.3 部署准备:从PyTorch权重到端侧模型转换
如果项目要在产线工控机上实时跑,显卡可能是老旧的GTX 1660Ti或者嵌入式设备RK3588,那就得提前考虑模型转换。yolov8的部署链路比较成熟,PyTorch权重先导出ONNX,再用各平台工具转成对应格式。对x86工控机,TensorRT加速很常见;对RK3588这类边缘盒子,需要转成RKNN格式。
# 导出ONNX,注意opset版本和动态轴设置 yolo export model=/data/el_runs/el_defect_v1/weights/best.pt format=onnx opset=12 dynamic=False imgsz=1280 # 如果是TensorRT,继续转engine trtexec --onnx=best.onnx --saveEngine=best.engine --fp16参数说明:opset=12对yolov8足够,太低会缺失某些算子;dynamic=False是为了推理速度牺牲灵活性,光伏产线输入分辨率固定,不需要动态尺寸。imgsz=1280必须和训练时一致,否则部署后精度会明显下降。这里有个血泪经验:转换后的ONNX模型在CPU上跑,预测结果和PyTorch原版偶尔会有细微差异,通常是因为某些算子在ONNX Runtime里实现精度不同,如果差太多,先检查输入图像的预处理是否一致——letterbox的填充值默认是114,yolov8在推理时默认也是这个,但自己写预处理脚本时很容易填成0。
5. 在项目里必须绕开的坑:光伏电池缺陷检测的实战避坑记
5.1 隐裂标注成多边形导致召回率上不去
现象:模型训练完,隐裂这一类的召回率始终在0.5以下,而其他类别都正常。检查混淆矩阵发现隐裂大量被漏检。原因:标注人员在labelme里用多边形精细勾勒隐裂轮廓,导致一个隐裂被切成了好几个碎片框,模型学到的目标是“短线段”而不是“整条裂纹”,推理时遇到完整裂纹反而不认得。解决:统一规范,细长缺陷一律用外接矩形整体框住。改完标注重新训练,召回率直接涨到0.8以上。
5.2 数据增强把断栅细节抹掉了
现象:断栅这一类在验证集上精确率很高,但一到产线真实图像就频繁漏检。原因:yolov8默认开启的mosaic增强会把四张图拼接并缩小,断栅这种密集平行线在缩小后纹理严重混淆,模型学到的是模糊特征。解决:训练时调低mosaic增强概率,mosaic=0.2,同时把hsv_h、hsv_s、hsv_v这几个颜色增强关闭——EL图像是电致发光灰度图,颜色扰动本来就无意义,纯属增加噪声。
5.3 验证集和训练集来自同一片电池串
现象:训练时mAP很高,超过0.95,但上新产线数据就拉胯。原因:光伏电池片的EL图像背景高度相似,如果训练集和验证集来自同一批次电池串,背景纹理几乎一样,模型学到的是“背景匹配”而不是“缺陷识别”。解决:按生产批次划分数据集,同一批次电池串的图像只允许出现在训练集或验证集中,绝不能混。这个操作叫“按批次切分”,在工业视觉项目里比随机划分重要得多。
5.4 正常样本太多导致误检一堆
现象:推理时产线图像上出现大量假阳性框,尤其黑斑类。原因:数据集里正常电池片的图像占了70%,而缺陷样本只有30%,模型训练时被背景样本主导,倾向于把所有异常纹理都判成缺陷。解决:正常样本也参与训练,但不用全部,按缺陷样本数量的1.5倍抽样即可;如果误检还压不住,可以在训练时给背景类设置较高置信度偏置,或者在推理脚本里把误检高发类别的conf阈值调高,比如把黑斑的conf提到0.35以上。
5.5 EL图像灰度归一化方式改变导致部署后精度下降
现象:训练时用OpenCV直接读图,np.mean自动做了0-255范围处理,部署时换成自研的C++图像处理管线,归一化方式变成除以65535,结果mAP暴跌。原因:EL相机有些输出16bit图像,存成PNG后OpenCV读进来像素值范围是0-65535,但yolov8训练时默认按8bit读图,像素范围变成0-255。如果标注和训练时用的是8bit图,部署管线就必须保证输入也转成8bit,否则模型看到的灰度分布完全不同。解决:在预处理脚本里强制cv2.imread(img_path, cv2.IMREAD_GRAYSCALE)后除以255,所有环节统一。
6. 可交付的最后一步:用类别级后处理压住误检
模型训练完、指标达标之后,项目真正交付前还差一道工序——按缺陷类型写后处理规则。直接裸跑模型不是不行,但产线验收时对误检零容忍。我给这个项目总结了一套很实用的后处理技巧:按缺陷的长宽比和面积范围做过滤。隐裂的预测框一定是长条形的,宽高比通常在3:1到20:1之间,如果一个被判定为隐裂的框宽高比接近1:1,大概率是误检。断栅的框有固定的水平条带特征,面积占比不会太大也不会太小。在推理脚本里加个筛选,缺陷框的物理尺寸明显超出合理范围就直接丢弃。
另外建议做一个置信度阈值的产品化策略:白天产线图像质量稳定,可以把对隐裂类的conf压到0.15,追求高召回;夜班图像噪点多,自动切换成0.25,压制假阳性。这块逻辑不复杂,却是我在这个项目里最后补上的一环——模型的mAP和产线的误报率是两回事,算法指标只解决了“能检测”的问题,可交付的关键在“让工人愿意用”。这是我做工业视觉项目跨过最大的坎,现在每次交付前都会专门留一周做阈值调优和坏例收集。希望帮到你。
本文还有配套的精品资源,点击获取