简介:基于Python实现的无人机光伏面板故障检测项目,为答辩评审98分的个人毕业设计,代码均经过调试测试,可稳定运行。资源主要面向计算机、人工智能、自动化等相关专业的学生与从业者,适用于期末课程设计、大作业或毕业设计,也适合小白的进阶学习。压缩包共409个文件、约106.71MB,核心为289个Python源文件,同时提供运行所需的dll、pyd、exe动态库与工具,以及txt、xml、cfg等配置和说明文档,目录结构清晰,便于逐模块阅读和复用。项目包含卷积神经网络模型与TensorFlow训练日志,完整覆盖数据集准备、模型训练、故障检测流程,能够直观理解无人机视角下光伏面板异常识别的实现思路,具有较强的工程参考价值。已有59人学习下载,基础扎实者还可在源码基础上扩展,适配更多检测场景。
1. 无人机光伏面板故障检测用 Python 做,是个被忽略的落地机会
无人机在光伏电站飞一圈,热红外镜头扫过一排排光伏板,回到地面上,Python 脚本自动把影像里的畸变和噪声处理掉,再用深度学习模型找到每一块热斑和隐裂,最后生成带经纬度标注的故障图——这正是“基于 python 实现无人机的光伏面板故障检测”这套项目要做的事。和常见的行人检测、车牌识别不同,光伏面板故障检测的难点不在模型结构,而在怎么把无人机视角变成模型能理解的训练数据,以及怎么让少量标注样本把召回率稳住。它适合两类人:一类是做毕业设计的学生,需要可复现、有工程背景、能写进论文的实验过程;另一类是光伏电站的运维或算法入门者,想用现成方案替代人工巡检。源码和文档说明的作用,是让你不用从零搭框架,而是把精力花在数据采集、调参和报告上。
2. 无人机光伏面板故障检测为什么先选红外与 YOLO:从成像特征到模型选型
2.1 热斑、隐裂、遮挡在图像上的差异决定了整个方案
做光伏面板故障检测,第一件事不是装模型,而是搞清楚你要检测的故障在哪一种图像里可见。如果选错成像方式,后面再多模型技巧都白费。
最常见的故障是热斑。光伏板被鸟粪、树叶、灰尘遮挡后,整串电池片里被遮挡的那一片会从发电变成耗电,温度显著升高,在红外热像仪下就是一块亮斑或暗斑。热斑在 RGB 图像里几乎不可见,只有在热红外波段才有稳定的对比度,所以做无人机巡检,优先配置热成像相机。第二个常见故障是隐裂,也就是电池片内部的细微裂纹。理想检测手段是电致发光(EL)成像,但 EL 需要给光伏板通电并在暗室拍摄,无人机根本做不到。比较现实的折中方案是:用可见光拍摄电池片表面的纹理变化,比如栅线断掉、电池片颜色发暗、表面出现异常线痕。第三个是遮挡和污染,这部分在可见光图像里非常直观,边界清晰,是深度学习最容易学会的一类故障。
也就是说,一个真正能落地的无人机检测方案,通常做成“红外检测热斑 + 可见光检测表面缺陷”的双光结构。如果条件有限,只做热斑检测也完全成立,因为热斑是电站最容易引发火灾的故障,也是运维方最关心的指标。在毕业设计里,先抱住热斑这一条线把项目做完整,远比贪多做得七零八落要好。
这里有一个常见的认知误区:有人会把红外图像直接转成灰度图,再套用普通 RGB 预训练模型。这样不是不行,但要注意红外图像的灰度分布和 visible 图像完全不同,预训练模型在 ImageNet 上学到的纹理特征并不完全适用。因此训练阶段应当从随机权重开始,或者在一个小的预训练权重基础上只训练红外数据,避免迁移学习带来的偏差。
2.2 深度学习模型选型:YOLO 系列是这个场景的性价比之王
传统图像处理不是不能做热斑检测。用 Otsu 阈值分割红外图像,晴天中午拍的热斑确实能被分离出来,但太阳角度一变、相机自动增益一动,灰度分布就会漂移,阈值马上失效。光伏巡检测试需要面对不同时间、不同辐照度、不同组件表面状态,传统方法很难建立一个稳定可靠的产品级方案。
深度学习方案里,YOLO 系列是实际工程中使用最多、最适合无人机光伏场景的选择。原因有几个:第一,光伏故障目标通常只占整张无人机图像的很小一部分,YOLO 能做到端到端的回归和分类,不像两阶段检测器那样先提候选框再细分类,对小目标更友好;第二,YOLO 系列在推理阶段速度极快,即便是没 GPU 的笔记本电脑,用 YOLOv8s 也能在 Jetson 或低功耗设备上跑出可用帧率;第三,生态成熟,标注格式、预训练权重、部署工具链都很全,源码改起来省事。
具体到 YOLOv5 和 YOLOv8 的选择,我一般推荐 YOLOv8。YOLOv8 是 anchor-free 的检测头,少了锚框设计这一层,回归目标变得直接,在光伏面板这类长宽比比较固定的目标上反而更好调;它的命令行接口也简单,训练、验证、导出 ONNX 一条命令都能完成。YOLOv5 的优势是资料多,很多老博客和开源部署教程都基于它,如果你的任务是去修改一个现成仓库,YOLOv5 也够用。但从毕业设计写论文的角度讲,YOLOv8 能拿出的对比实验和可视化结果更丰富,答辩时更好讲。
模型规模的底线也要说清楚。光伏故障检测的目标非常小,YOLOv8n 这个 nano 级别虽然在 COCO 上速度最快,但在面板边缘裂纹这类目标上很容易欠拟合。我实际项目中至少用 YOLOv8s,显存允许就上 YOLOv8m。模型大小多出的几毫秒推理时间,在无人机巡检场景里根本不敏感,因为一遍巡检要处理几千张图,真正耗时的是预处理而不是单张推理。
2.3 检测系统整体架构:一条链路从无人机影像到故障报告
源码里的程序结构应当围绕一条完整链路来组织,否则很容易变成“只有训练代码的残废项目”。常见的做法是:无人机按规划航点飞行,自动采集红外与可见光影像,落地后把影像按航点编号导出到文件夹;Python 端先做预处理,比如 jpg 格式转换、镜头畸变校正、图像去条纹、按光伏阵列方向旋转;然后把原图切成小块送进 YOLO 模型推理;推理结果经过 NMS 和规则过滤后,把热斑框叠加到原图上,生成可视化报告和 CSV 表格。
实时处理在无人机光伏巡检里其实不是硬需求。无人机飞行速度、相机帧率、存储卡写入速度决定了数据量,一般一块 2 平方公里的光伏电站,飞完一次会产生几千张 640×512 或 5120×3840 的图片。真正的瓶颈是后处理阶段能否在半小时内跑完,所以架构上不追求实时流式处理,而是先落盘、后批处理,这样也能避免飞机飞行途中算力不足的问题。
无人机路径规划算法在这个项目里是加分项。如果你用大疆等商业飞控,可以直接用地面站软件生成弓字形自动巡检航线,确保相邻航线的重叠率达到 70% 以上,这样后期做图像拼接或者切片才不会出现漏检。如果自己写航线规划,则需要把飞行高度、云台俯仰角、航向重叠率写成一个配置文件,让后续的影像解析脚本能拿到每张图对应的 POS 信息。这部分逻辑看似简单,但直接影响后面把像素坐标换算成经纬度坐标的精度。
3. 把无人机影像做成 YOLO 数据集:标注格式转换与四个增强手段
3.1 采集规范:高度、重叠率、时段对检测效果影响很大
很多人在源码跑通后第一反应是拿网上公开数据集来训练,但这些数据集里的光伏板是屋顶组件或者地面电站的远景图,跟无人机俯拍视角完全不同。光伏面板故障检测是强视角相关的任务,俯拍和斜拍下,面板边框的形状、热斑的纹理完全不同。因此第一步是自建数据,而自建数据的质量由采集参数直接决定。
飞行高度是最先要固定的参数。我一般把无人机保持在离光伏阵列平面 30 到 60 米的高度。太低导致单张图覆盖面积小,数据量爆炸;太高导致热斑在图像上只占几个像素,模型学不到有效特征。60 米高度用 640×512 红外相机拍摄时,一块 2 米乘 1 米的光伏板在图像里大约只有 40×20 像素,此时热斑区域已经小于 32×32,对检测器算是小目标。如果继续升高到 100 米,目标直接变成 8×8 像素,这时候人类肉眼都无法确认热斑,只能对大范围组件整体温度做统计,不适合做端到端的框检测。
重叠率也是个不能省的数字。相邻航片之间的航向重叠率和旁向重叠率都要保持在 70% 以上,这样既方便后期用 OpenCV 拼接,也能确保同一个故障至少出现在两张图里。不然热斑恰好落在两张影像的边缘,一半在上一张、一半在下一张,模型对这类边缘目标天然不敏感。飞行时段最好选在正午前后两个小时内,此时太阳辐照度高且相对稳定,正常电池片温度高,被遮挡的电池片发热明显,红外温差最大。阴天或者清晨时段,热斑温差只有两三度,红外图上灰度对比非常弱,模型基本学不出来。
采集完看一遍原始数据,我建议先用软件把红外视频抽帧保存成 jpg,同时检查有没有拖影。无人机快速转向时云台跟不上,画面会模糊,这类图片直接删掉,不要混进训练集,否则模型会学到“模糊区域 = 故障”这种错误关联。
| 采集参数 | 推荐值 | 影响 |
|---|---|---|
| 飞行高度 | 30–60 米 | 决定 GSD 与目标像素尺寸 |
| 航向重叠率 | ≥70% | 影响故障是否被完整拍到 |
| 旁向重叠率 | ≥70% | 影响拼接和切片时的连续 |
| 拍摄时段 | 10:00–14:00 | 保证热斑与正常区温差最大 |
| 红外图像分辨率 | 640×512 以上 | 太低则热斑面积不足 |
3.2 标注与格式转换:把 VOC XML 转成 YOLO txt 的脚本
自建数据最常见的标注工具是 LabelImg 或 X-AnyLabeling。LabelImg 导出的是 Pascal VOC 格式的 XML 文件,而 YOLO 训练需要的是每张图对应一个 txt 文件,里面每行是“类别 中心点x 中心点y 宽 高”,坐标都是归一化到 0 到 1 的浮点数。这里给一个可复用的转换脚本,用于把 XML 批量转成 YOLO 格式的 txt。
import xml.etree.ElementTree as ET import os def voc_xml_to_yolo_txt(xml_path, output_dir, class_map=None): """ 将 LabelImg 导出的 VOC XML 转换为 YOLO 格式 txt。 xml_path : 单个 XML 文件路径 output_dir : 输出 txt 的目录 class_map : 类别映射字典,例如 {"hotspot": 0, "crack": 1, "dirt": 2} """ if class_map is None: class_map = {"hotspot": 0} tree = ET.parse(xml_path) root = tree.getroot() size = root.find("size") img_w = int(size.find("width").text) img_h = int(size.find("height").text) lines = [] for obj in root.iter("object"): name = obj.find("name").text if name not in class_map: continue box = obj.find("bndbox") x_min = float(box.find("xmin").text) y_min = float(box.find("ymin").text) x_max = float(box.find("xmax").text) y_max = float(box.find("ymax").text) # 计算中心点和归一化宽高 x_center = (x_min + x_max) / 2.0 / img_w y_center = (y_min + y_max) / 2.0 / img_h w = (x_max - x_min) / img_w h = (y_max - y_min) / img_h # 防止标注框边界溢出图像 x_center = min(max(x_center, 0.0), 1.0) y_center = min(max(y_center, 0.0), 1.0) w = min(w, 1.0 - x_center) h = min(h, 1.0 - y_center) lines.append(f"{class_map[name]} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}") out_path = os.path.join(output_dir, os.path.splitext(os.path.basename(xml_path))[0] + ".txt") with open(out_path, "w", encoding="utf-8") as f: f.write("\n".join(lines)) if __name__ == "__main__": # 用法示例:遍历某个目录下所有 XML,逐个转换 xml_dir = "label_xml" txt_dir = "yolo_labels" os.makedirs(txt_dir, exist_ok=True) for xml_file in os.listdir(xml_dir): if xml_file.endswith(".xml"): voc_xml_to_yolo_txt(os.path.join(xml_dir, xml_file), txt_dir)脚本逻辑不复杂,但两个参数值得说明。一是root.iter("object"),比root.findall("object")更健壮,因为 XML 里 object 可能嵌套在其它节点里,用 iter 可以遍历所有层级,避免漏标。二是坐标归一化时务必拿 XML 里的原始图片宽高,不要拿程序里读取的图像宽高,两者在 JPEG 旋转 EXIF 信息存在时可能不一致。
转换完成后,必须抽查几张图,把 txt 里的坐标还原成矩形画到原图上,确认没有错位。这个验证步骤很多人省掉,结果训练时 loss 降不下去,最后发现是 300 张标注文件里面 50 张的宽高搞反了。这类低级错误花费的时间远超写脚本的时间。
3.3 数据增强:哪些操作有效,哪些操作会把红外图搞坏
光伏面板是高度规则的几何对象,增强操作不能盲目照搬 COCO 那套。Mosaic 增强会把四张不同场景的光伏板拼在一起,模型容易学到拼接边界上的错误纹理;MixUp 叠加后热斑和正常区域灰度混在一起,标注语义被稀释。因此,我更推荐用几何变换和亮度对比度变换,这两种对红外图相对安全。
import albumentations as A def build_transforms(mode="train", img_size=640): if mode == "train": return A.Compose([ A.RandomResizedCrop( height=img_size, width=img_size, scale=(0.5, 1.0), ratio=(0.8, 1.2), p=0.8, ), A.HorizontalFlip(p=0.5), A.VerticalFlip(p=0.3), A.RandomBrightnessContrast( brightness_limit=0.15, contrast_limit=0.15, p=0.7, ), A.Rotate(limit=15, p=0.3), ]) else: return A.Compose([ A.Resize(height=img_size, width=img_size), ])参数上要注意RandomResizedCrop的scale不要低于 0.5。光伏板上的热斑本来就只有几十像素,如果随机裁剪到 0.3 倍再放大回来,目标规模严重失真,等于人为制造了一个“大目标质检器”去检测小目标,训练和推理尺度不一致。Rotate的旋转角度限制在正负 15 度以内也很有必要,因为无人机巡检时习惯让光伏板边缘与图像坐标轴大致平行,旋转角太大会让边框变成斜线,模型被迫去学各种倾角,数据效率反而变差。
亮度对比度增强对红外图是双刃剑。热斑的本质是局部灰度异常,适度调整亮度和对比度可以模拟不同季节、不同太阳高度角下的红外灰度变化,增强模型对灰度偏移的鲁棒性。但幅度过大会把正常电池板之间由于安装角度造成的温度差异也放大,产生大量伪热斑。实践下来brightness_limit=0.15是一个比较安全的经验值,再大就要用真实拍摄不同时段的数据来补,而不是靠增强硬凑。
4. 训练 YOLOv8 检测光伏故障:一组能收敛的参数与三种调优手段
4.1 从 YOLOv8 最小训练命令到超参表
环境准备这里不多展开,但需要提醒的是,ultralytics 包对 Python 版本有要求,建议先看官方文档把 python 版本和依赖装齐,再用下面命令验证环境:
yolo detect predict model=yolov8s.pt source=test.jpg能正常出框说明安装没有问题。接下来开始训练,下面这组参数是我在光伏缺陷数据集上测试过、能稳定收敛的起点:
yolo detect train \ data=dataset.yaml \ model=yolov8s.pt \ epochs=150 \ batch=8 \ imgsz=640 \ lr0=0.005 \ lrf=0.01 \ patience=30 \ cache=Truedataset.yaml需要把路径和类别写清楚,常见的错误是train路径写成了绝对路径,换机器跑就得改一遍;建议写成相对路径,并和数据集放在同一个工程目录下。model=yolov8s.pt表示从 COCO 预训练权重继续训练,训练会快很多;如果你想验证红外图像迁移学习的影响,可以改成model=yolov8s.yaml从零训练做对比实验。
| 参数 | 推荐值 | 作用解读 |
|---|---|---|
| epochs | 150 | 光伏数据集通常只有几千张,训练 150 轮足够拟合,太多容易过拟合 |
| batch | 8 | 感知显存大小可调;显存不够时优先降 batch,而不是降 imgsz |
| imgsz | 640 | 640 是速度与精度的平衡点;裂缝检测可试 1280,但需要更高显存 |
| lr0 | 0.005 | 比默认 0.01 更保守,小数据集防止训练初期发散 |
| lrf | 0.01 | 让学习率在最后阶段衰减到初始值的 1/100,收敛更稳 |
| patience | 30 | 验证集指标连续 30 轮不涨就提前停止,省时间 |
| cache | True | 把图片提前缓存到内存,减少磁盘 IO,数据集不大时强烈建议开启 |
如果你只有 6G 显存,batch 改成 4,imgsz 可以保持 640。cache=True在内存小于 8G 的机器上不要开,否则会触发内存交换,训练速度反而更慢。训练结束后到runs/detect/train目录下看results.png和weights/best.pt,best.pt是按验证集指标选出来的最优权重,后续推理统一用它。
4.2 小目标检测专项优化:切片训练与锚框调整
在无人机光伏巡检里,目标小是固有属性。一个 640×640 的红外图像块里,热斑有时只占 20×15 像素,隐裂线更是只有几个像素宽。此时直接套用默认训练策略,模型会把整个光伏板判为背景,漏检率很高。我一般先做一个标注框面积统计分析,确认小目标占比,再决定要不要切片。
import os def analyze_boxes(label_dir, img_size=640): total = 0 small_area = 0 tiny_area = 0 for txt in os.listdir(label_dir): if not txt.endswith(".txt"): continue with open(os.path.join(label_dir, txt), "r", encoding="utf-8") as f: for line in f: parts = line.strip().split() if len(parts) < 5: continue _, _, _, w, h = parts w = float(w) * img_size h = float(h) * img_size area = w * h total += 1 if area < 32 * 32: small_area += 1 if area < 8 * 8: tiny_area += 1 print(f"total boxes: {total}") print(f"small boxes (<32x32): {small_area / total:.3f}") print(f"tiny boxes (<8x8): {tiny_area / total:.3f}")如果 small_area 占比超过 40%,最直接的办法是把图切成 320×320 的小块再做训练和推理。这样原本 20×15 像素的热斑在切片后变成 40×30 像素,目标相对尺寸放大了两倍,模型学起来轻松得多。切片时注意两个细节:一是相邻切片要有 10% 的重叠,防止目标正好卡在切片边界;二是标注框在切片后的坐标要重新计算,保留那些中心落在切片内的框,删除已经被边缘截断大半的框。
另一种可选方案是提升训练分辨率到 1280 而不是 640,但这对显存的要求高不少,而且训练和推理都变慢。在毕业设计的算力限制下,切片训练通常是性价比最高的选择,既不需要换显卡,也不需要改模型结构。
4.3 评估指标:不要只盯 mAP,要看被漏掉的小目标
训练完成后,用下面命令评估验证集:
yolo detect val \ model=runs/detect/train/weights/best.pt \ data=dataset.yaml \ imgsz=640输出会显示每个类别的 mAP50、mAP50-95、precision、recall。我一般重点关注 recall 而不是 mAP,因为在光伏运维场景里,漏掉一个热斑可能导致整串组件烧毁,漏检的代价远比误检大。如果召回率只有 0.6,说明一半多的热斑根本没被模型看见,这时再调置信度阈值也无济于事,应该回到数据层面做切片或补充样本。
看 PR 曲线时,密切关注曲线左侧的下降段。热斑类别如果 precision 高、recall 低,说明模型只在非常有把握的时候才输出,这类高判别的行为在巡检中并不好用。相反,如果 recall 高、precision 低,误检多一些反而更好办,因为可以加后处理规则把边框、支架等误检过滤掉。在答辩汇报里,这样一个“先拉召回、再压误检”的调优故事,比直接交一个 mAP 数字更有说服力。
5. 无人机采集与模型部署避坑记录:五个让人想删库重来的瞬间
5.1 红外与可见光没对齐,标注数据白做了
现象:双光相机拍的同一块光伏板,在红外图和可见光图上位置相差几十个像素。你按可见光标注,转成红外图后框就落在错误区域,模型学到的特征完全是噪声。
原因:双光相机物理位置不同导致视差,两个镜头的焦距和畸变也不一样;而且无人机云台在飞行中会轻微抖动,两路画面的平移关系不是固定常数。
解决:在标注前先做一次配准。常见做法是在每段视频开始前拍一张棋盘格或光伏阵列支架的角点图,用四点单应性变换把红外图映射到可见光坐标系:
import cv2 import numpy as np H, _ = cv2.findHomography(src_pts, dst_pts, method=cv2.RANSAC) aligned_ir = cv2.warpPerspective(ir_img, H, (vis_img.shape[1], vis_img.shape[0]))findHomography的src_pts和dst_pts是你在两张图上手工选取的至少 4 组对应点,优先选光伏板四角、支架关节等角点明显的特征。配准后一定要把两张图叠在一起半透明检查,肉眼看到边缘重合后再开始标注。血泪经验:不要相信相机厂商给出的“出厂已对齐”,温度变化和云台震动都会让出厂标定失效。
5.2 光伏板边框被识别成裂缝,误报率飙到 30%
现象:模型训练完,验证集 mAP 不错,但拿到实拍大图上跑,铝合金边框和光伏板之间的接缝被大量标成 crack,误报率直接飙到 30%。
原因:边框在可见光图像中是高亮长直线,和隐裂在边缘、灰度、长宽比上高度相似;训练阶段负样本太少,模型没机会见到“边框”这样的干扰项。
解决:三步同时做。第一,标注时不要把边框标成故障类别,同时在边框区域附近多截取一些背景块加入训练集;第二,训练时用负样本挖掘,把误报高的图片单独挑出来继续训练几个 epoch;第三,后处理阶段用 OpenCV 的直线检测过滤掉与边框重合的检测框:
def filter_by_edge_overlap(boxes, edge_map, overlap_thresh=0.3): keep = [] for box in boxes: x1, y1, x2, y2 = box region = edge_map[y1:y2, x1:x2] if region.mean() > overlap_thresh: continue keep.append(box) return keepedge_map可以通过 Canny 边缘检测得到,然后算检测框内部边缘像素的平均强度。光伏板边框区域边缘密度远高于正常的电池片表面,这个简单规则就能过滤掉大部分边框误检,而且不影响真实裂缝检测。
5.3 飞行高度一变,检测效果断崖式下降
现象:在 45 米高度拍的数据上训练模型,mAP 达到 0.82。换到 80 米高度重新飞一遍,召回率直接掉到 0.3,很多热斑漏检。
原因:目标尺度变了。45 米高度热斑在图像里约 30×30 像素,80 米高度只剩 15×15 像素,模型学习到的特征尺度完全不匹配。深度学习模型对尺度变化比人类敏感得多,尤其是热斑这种纹理信息很少的目标。
解决:要么固定飞行高度,要么在训练时打开多尺度训练。ultralytics 默认有 scale 增强,但不一定覆盖到无人机高度变化这么大的尺度差。最稳妥的办法是把训练图片按 0.5 到 1.5 倍缩放后分别训练,再把测试时飞行高度作为配置文件里的固定参数,不允许操作人员随意改。如果部署时换了大疆等不同机型的飞机,镜头焦距不同,GSD 也会变,需要重新采集少量数据做微调,不要指望一个模型通吃所有平台。
5.4 训练 Loss 不降,标注框太小或样本不均衡
现象:训练开始 loss 在 0.6 左右,跑了 30 个 epoch 基本不变,验证集 precision 和 recall 都是 0。
原因:检查标注后发现问题出在标注框过小。比如有些隐裂标注只有 3×20 像素,经过 YOLO 的 8 倍下采样后,特征图上的响应只有不到 3 个像素,模型根本无法学习;另外,hotspot 类别只占全部标注框的 2%,模型倾向于把所有区域都预测为背景。
解决:先用标注分析脚本把面积小于 8×8 像素的框找出来。对于这些目标,要么把图像切片放大后再标注,要么直接剔除。类别不平衡可以给少数类设置更高的 loss 权重,ultralytics 中可以通过在dataset.yaml里配置不同类别的权重,但更有效的方法是采集更多缺陷样本做过采样。如果样本量实在不足,用 5.1 的数据增强对少数类单独做几何变换,扩充到 3 倍以上。
5.5 文档说明是毕设的隐藏加分项,记得把实验表写进去
现象:答辩老师问“超参数怎么来的?你试过哪几种组合?为什么最终选这组?”,很多同学答不上来,只说“参考开源默认值”。
原因:训练过程全在命令行里跑,没有记录每次实验的配置和结果。代码再漂亮,讲不出调参过程,答辩一样吃亏。
解决:从第一次训练开始就建一个实验记录表,至少包含模型版本、imgsz、epochs、batch、是否切片、mAP50、recall、训练时间、备注。每跑完一组实验花三分钟填一行,最后写文档时直接把这些数据转成论文里的对比表。这套“源码 + 文档说明”的交付物,在毕设评审里往往比模型本身更能撑场面,因为老师能清楚看到你的实验逻辑,而不是只能看到一段能跑的代码。
6. 让检测结果落到地图上:SAHI 切片推理与 POS 坐标定位的落地技巧
6.1 用 SAHI 对大图做切片推理
训练阶段的切片优化解决了小目标问题,但推理阶段如果直接把大图缩放到 640,热斑仍然会被压缩丢失。SAHI(Slicing Assisted Hyper Inference)是专门解决这类大图小目标推理的工具,它会把原图切成多个重叠切片分别推理,然后再合并结果。YOLOv8 模型接 SAHI 的用法如下:
from sahi import AutoDetectionModel from sahi.predict import get_sliced_prediction model = AutoDetectionModel.from_pretrained( model_type="yolov8", model_path="runs/detect/train/weights/best.pt", confidence_threshold=0.3, image_size=1280, ) result = get_sliced_prediction( "thermal_full.jpg", model, slice_height=320, slice_width=320, overlap_height_ratio=0.2, overlap_width_ratio=0.2, postprocess_class_agnostic=False, )slice_height和slice_width决定了切片大小,320 在大多数情况下够用;overlap_height_ratio=0.2让相邻切片有 20% 重叠,避免缺陷被切断。postprocess_class_agnostic=False表示不同类别分别做 NMS,防止热斑和裂缝两类目标互相压制。用 SAHI 处理 5120×3840 的大图时,效果通常比直接整图缩放提升明显,代价是推理时间变长,但巡检场景完全能接受。
6.2 结合 POS 数据输出故障经纬度
检测出故障框只是第一步,运维人员真正关心的是故障在哪一排光伏板、哪个经纬度。无人机 POS 文件里记录了每张影像的经纬度、高度、飞行器偏航角、云台俯仰角。在相机近似垂直向下拍摄的前提下,可以用像素坐标换算地理坐标:
import math def pixel_to_geo(cx_px, cy_px, img_w, img_h, pos, gsd_m_per_px, yaw_deg): dx = (cx_px - img_w / 2) * gsd_m_per_px dy = (img_h / 2 - cy_px) * gsd_m_per_px yaw = math.radians(yaw_deg) east = dx * math.cos(yaw) - dy * math.sin(yaw) north = dx * math.sin(yaw) + dy * math.cos(yaw) lat = pos["lat"] + north / 111320.0 lon = pos["lon"] + east / (111320.0 * math.cos(math.radians(pos["lat"]))) return lat, lon这个公式把像素坐标按 GSD 等比缩放到地面距离,再按偏航角旋转到地理坐标系。它是垂直拍摄假设下的近似换算,飞控云台俯仰角不为 0 时会引入误差,误差大约在 2 到 5 米范围,但对于标定“哪一排组件出了问题”已经足够。如果毕设时间充足,可以用相机内参做更严格的共线方程投影,但最终呈现出来的效果差别不大,优化性价比不高。
6.3 用温度差验证检测结果
模型输出的热斑框是否真正对应故障,可以用图像内温度场做二次校验。热斑本质是温度异常,红外图在未标定温度时也能反映相对灰度差异。对每个检测框,计算框内平均灰度与整张图背景平均灰度的差异,如果差异低于一个阈值,说明这个框大概率是误检,可以过滤掉。这个规则在白天强辐照时尤其可靠,因为正常电池片之间温差通常很小。经过这一道校验,误检率能再降一截。
我现在做这类项目,习惯第一周不写训练代码,先把航线设计、数据采集规范、标注格式、实验记录表四样东西定下来。数据、实验、文档各留出足够时间,后面返工的几率会小很多。希望这套从数据到部署的路线对你有帮助。
本文还有配套的精品资源,点击获取