ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

光伏板故障检测:YOLOv5s+ResNet18双路视觉系统实战

光伏板故障检测:YOLOv5s+ResNet18双路视觉系统实战 简介本资源是一套基于Python实现的无人机光伏面板故障检测完整项目面向计算机、通信、人工智能及自动化等专业的本科生与研究生适用于毕业设计、课程大作业及工程实践学习。项目已通过高分答辩98分代码经充分调试验证可直接运行兼顾入门学习与二次开发需求。压缩包共409个文件主体为289个Python源码文件含模型训练、图像识别、无人机控制逻辑等核心模块辅以52个DLL动态库、22个PYD加速模块、11个EXE可执行程序及配置类文件CFG、BAT、XML等整体体积106.71MB结构完整涵盖环境配置、模型权重PTH、PB、checkpoint、TensorFlow事件日志events.out.tfevents及依赖管理脚本。目前已有59人下载学习提供从数据采集、缺陷识别到结果可视化的一站式实现方案并附详细文档说明特别适合理解CV嵌入式协同检测的实际落地路径。1. 这不是“无人机Python”拼凑的玩具项目它用真实航拍图检测光伏板隐裂、热斑、遮挡跑通全流程含TensorFlow模型固化、OpenCV图像预处理链、YOLOv5s轻量化部署你可能见过几十个标着“无人机Python”的毕业设计压缩包——点开全是空文件夹、报错截图、PPT里飞在天上的示意图。但这个项目不一样它用实拍的237张无人机俯视光伏阵列图含14类典型故障样本在RTX 3060笔记本上完成端到端推理平均单图耗时187msmAP0.5达82.3%。核心不是炫技而是解决一个真问题光伏电站巡检中人工肉眼漏检率超35%而传统红外热成像设备单台动辄20万。本项目把成本压到3000元内大疆Mini 3 Pro 树莓派4B用纯Python栈实现从图像采集→畸变校正→多尺度分割→CNN分类YOLO定位→结果可视化全链路。适合计算机/自动化/新能源方向学生复现毕设也适合现场工程师快速验证算法可行性——我去年帮某光伏运维团队落地时直接替换了他们原用的Matlab脚本误报率下降41%。别被标题里的“高分毕业设计”误导它本质是个可工程化的小型工业视觉系统。2. 为什么选YOLOv5sResNet18双路架构故障类型分布不均、小目标密集、光照干扰强的硬约束倒逼出这套组合2.1 光伏面板故障的三大物理特性决定了模型不能照搬通用目标检测方案光伏板故障检测和COCO数据集有本质差异目标尺度极端不均热斑直径常为3–8像素占整图0.02%而遮挡物鸟粪、树叶可达整板面积30%类别严重长尾隐裂样本占68%但热斑仅占7%传统交叉熵损失会让模型忽略热斑光照伪影干扰强正午反光、云层阴影、镜头眩光在RGB图中形成与故障相似的亮斑/暗区。我们放弃YOLOv8或DETR这类新模型原因很实际YOLOv5s在Jetson Nano上实测FPS达24而YOLOv8n在相同硬件下掉到11DETR训练需32GB显存毕设环境根本跑不动。ResNet18作为辅助分类分支专门处理YOLO漏检的小热斑——它不负责定位只对YOLO输出的候选框做二分类正常/热斑用Focal Loss缓解长尾问题。2.2 源码结构解析6个核心模块如何协同工作项目目录严格按工业视觉流水线组织非教学式扁平结构├── data/ # 原始数据与标注 │ ├── raw_drone_imgs/ # 237张1920×1080航拍图含EXIF地理信息 │ └── labels/ # PASCAL VOC格式XML YOLO格式txt双备份 ├── src/ │ ├── preprocess/ # 关键含畸变校正、白平衡、阴影抑制 │ │ ├── undistort.py # 使用相机内参矩阵校正鱼眼畸变附标定棋盘格图 │ │ └── shadow_remove.py # 基于Retinex算法改进的阴影抑制比CLAHE更稳 │ ├── model/ # 双模型架构 │ │ ├── yolov5s/ # 修改版YOLOv5s增加GELU激活、通道剪枝 │ │ └── resnet18/ # 轻量ResNet18输入224×224输出2类概率 │ ├── inference/ # 推理主流程 │ │ └── pipeline.py # 串联预处理→YOLO→ResNet→后处理→可视化 │ └── utils/ # 工程化工具 │ ├── geo_utils.py # 将像素坐标转为经纬度调用GPS_EXIF解析 │ └── eval_metrics.py # 自定义mAP计算支持小目标IoU阈值0.3 └── docs/ ├── system_design.pdf # 系统架构图含数据流、模块依赖、硬件选型依据 └── deployment_guide.md # 树莓派4B部署步骤含OpenCV编译参数避坑提示preprocess/undistort.py中的相机内参矩阵K [[1200, 0, 960], [0, 1200, 540], [0, 0, 1]]是实测标定值不是网上随便抄的。若换无人机型号必须重新标定否则定位误差超2米。2.3 模型训练的关键参数配置非默认值训练脚本src/model/yolov5s/train.py的核心修改# train.py 关键参数对比YOLOv5官方默认值 parser.add_argument(--batch-size, typeint, default16) # 官方默认32但显存不足时OOM parser.add_argument(--imgsz, typeint, default640) # 官方默认640但光伏板纹理细升到768提升小目标召回 parser.add_argument(--lr0, typefloat, default0.01) # 官方默认0.01但长尾数据需warmup见下方scheduler parser.add_argument(--epochs, typeint, default200) # 官方默认300早停策略在187轮触发 # 自定义学习率调度器解决长尾问题 def get_lr_scheduler(optimizer): return torch.optim.lr_scheduler.OneCycleLR( optimizer, max_lr0.02, epochs200, steps_per_epochlen(train_loader), pct_start0.1, # 前10%轮次warmup避免热斑样本初始梯度爆炸 div_factor10, # 初始lr0.002 final_div_factor1000 )这段代码的玄学在于pct_start0.1—— 我试过0.05和0.15前者导致热斑loss震荡后者让隐裂收敛变慢。最终选择0.1是因热斑样本在前20轮梯度最大需要足够warmup时间。3. 预处理链不是“调个contrast”畸变校正阴影抑制动态白平衡三步缺一不可3.1 畸变校正为什么OpenCV的cv2.undistort()在这里失效大疆Mini 3 Pro的广角镜头畸变非标准径向模型直接调用cv2.undistort()会导致边缘光伏板拉伸变形。我们改用cv2.fisheye.undistortImage()并传入实测标定参数# src/preprocess/undistort.py import cv2 import numpy as np def fisheye_undistort(img_path, K, D, new_KNone): K: 相机内参矩阵 (3x3) D: 畸变系数 [k1,k2,k3,k4] (4,) new_K: 校正后内参设为None则自动计算 img cv2.imread(img_path) h, w img.shape[:2] # 关键new_K必须重算否则ROI裁剪错误 if new_K is None: new_K cv2.fisheye.estimateNewCameraMatrixForUndistortRectify( K, D, (w, h), 1, (w, h) ) map1, map2 cv2.fisheye.initUndistortRectifyMap( K, D, np.eye(3), new_K, (w, h), cv2.CV_16SC2 ) undistorted cv2.remap(img, map1, map2, interpolationcv2.INTER_LINEAR) # 裁剪黑边fisheye校正必有黑边 x, y, w_crop, h_crop cv2.boundingRect( cv2.cvtColor(undistorted, cv2.COLOR_BGR2GRAY) ) return undistorted[y:yh_crop, x:xw_crop] # 实测标定参数Mini 3 Pro 4K模式 K np.array([[1200.0, 0.0, 960.0], [0.0, 1200.0, 540.0], [0.0, 0.0, 1.0]]) D np.array([-0.28, 0.08, -0.01, 0.005]) # k1,k2,k3,k4逻辑说明cv2.fisheye.undistortImage()比cv2.undistort()更适合鱼眼镜头但必须配合initUndistortRectifyMap()生成映射表。参数D中的k1-0.28是负值说明存在桶形畸变广角常见若填正值会彻底翻车。3.2 阴影抑制Retinex算法为何比CLAHE更适合光伏场景光伏板阴影常呈渐变状如云层投影CLAHE会过度增强边缘噪声。我们采用单尺度RetinexSSR改进版# src/preprocess/shadow_remove.py def ssr_enhance(img, sigma30): SSR: R(x,y) log(I(x,y)) - log(I(x,y) * G_sigma) sigma30适配光伏板尺寸实验确定sigma20去不净阴影40损失纹理 img_float img.astype(np.float32) 1.0 # 避免log(0) log_img np.log(img_float) # 高斯模糊核sigma30对应约90px半径覆盖典型云影尺寸 kernel_size int(2 * sigma * 2 1) blurred cv2.GaussianBlur(img_float, (kernel_size, kernel_size), sigma) log_blurred np.log(blurred 1.0) # SSR增强增强对比度抑制低频阴影 enhanced np.exp(log_img - log_blurred) return np.clip(enhanced, 0, 255).astype(np.uint8) # 应用流程 img cv2.imread(raw.jpg) undistorted fisheye_undistort(img, K, D) shadow_free ssr_enhance(undistorted) # 注意必须在undistort后执行参数说明sigma30是血泪经验——测试过10/20/30/40/50sigma30时热斑信噪比提升最显著PSNR从18.2dB升至24.7dB且光伏板金属边纹路无断裂。3.3 动态白平衡解决不同时间段拍摄的色偏问题正午蓝光强、傍晚红光强直接导致同一故障在不同时间呈现不同颜色。我们不用简单灰度世界法而采用基于光伏板区域的自适应白平衡# src/preprocess/white_balance.py def adaptive_wb(img, panel_mask): panel_mask: 二值图1光伏板区域由HSV阈值形态学闭运算生成 # 提取光伏板区域像素排除天空/地面干扰 panel_pixels img[panel_mask 1] # 计算各通道中位数比均值抗异常值 r_med np.median(panel_pixels[:, 2]) g_med np.median(panel_pixels[:, 1]) b_med np.median(panel_pixels[:, 0]) # 以绿色通道为基准调整R/B增益 r_gain g_med / (r_med 1e-6) b_gain g_med / (b_med 1e-6) balanced img.copy() balanced[:, :, 2] np.clip(balanced[:, :, 2] * r_gain, 0, 255) balanced[:, :, 0] np.clip(balanced[:, :, 0] * b_gain, 0, 255) return balanced.astype(np.uint8)关键点panel_mask必须精准——我们用HSV空间的S30 and V50提取光伏板避开反光区域再经cv2.morphologyEx(..., cv2.MORPH_CLOSE)闭运算填充孔洞。若mask不准白平衡会把热斑当正常区域校正掉。4. 模型推理PipelineYOLOv5s定位ResNet18二次分类如何规避小目标漏检4.1 双路检测逻辑为什么不用单模型端到端YOLOv5s对10px目标召回率92%但对3–8px热斑仅61%。若强行增大输入分辨率如1280×720GPU显存爆掉。双路设计是工程妥协YOLOv5s主路输入768×768输出所有5px目标隐裂、遮挡、破损等ResNet18辅路对YOLO输出的每个候选框裁剪224×224子图送入ResNet18专判是否为热斑融合策略YOLO置信度0.5且ResNet热斑概率0.7才标记为热斑。# src/inference/pipeline.py 核心逻辑 def run_inference(img_path): # 步骤1预处理畸变校正→阴影抑制→白平衡 img preprocess_pipeline(img_path) # 步骤2YOLOv5s推理返回boxes: [x1,y1,x2,y2,conf,cls] yolov5_results yolov5_model(img) # 自定义封装支持FP16加速 # 步骤3对每个box裁剪并送入ResNet18 final_detections [] for box in yolov5_results: x1, y1, x2, y2, conf, cls box if cls 0: # 0热斑类YOLO标签映射 crop img[int(y1):int(y2), int(x1):int(x2)] if crop.size 0: continue # ResNet18输入需resize归一化 crop_resized cv2.resize(crop, (224, 224)) crop_norm (crop_resized.astype(np.float32) / 255.0 - np.array([0.485, 0.456, 0.406])) / np.array([0.229, 0.224, 0.225]) resnet_prob resnet_model(torch.from_numpy(crop_norm).permute(2,0,1).unsqueeze(0)) if resnet_prob[0][1] 0.7: # 1热斑概率 final_detections.append([x1,y1,x2,y2,conf*resnet_prob[0][1], hotspot]) return final_detections参数说明resnet_prob[0][1] 0.7是阈值实验结果——0.6时误报率12%0.8时漏检率升至33%0.7是平衡点。注意conf*resnet_prob[0][1]是联合置信度非简单相乘因YOLO置信度已含定位可靠性。4.2 后处理NMS之外必须加的两道过滤YOLO原始NMSIoU0.45仍会残留重复框我们增加光伏板级去重同一块光伏板内若多个框中心点距离50px保留置信度最高者地理一致性校验利用EXIF中的GPS坐标若相邻帧同位置故障置信度差0.3触发人工复核。# src/utils/geo_utils.py def filter_by_panel_density(detections, panel_map, threshold_px50): panel_map: 每像素所属光伏板ID的二维数组由轮廓检测生成 filtered [] for det in detections: x_c int((det[0] det[2]) / 2) y_c int((det[1] det[3]) / 2) panel_id panel_map[y_c, x_c] if 0 y_c panel_map.shape[0] else -1 # 同一panel内找近邻 same_panel [d for d in filtered if panel_map[int((d[1]d[3])/2), int((d[0]d[2])/2)] panel_id] if not same_panel: filtered.append(det) else: # 计算中心距删低置信度者 dists [np.sqrt((x_c-int((d[0]d[2])/2))**2 (y_c-int((d[1]d[3])/2))**2) for d in same_panel] if all(d threshold_px for d in dists): filtered.append(det) else: # 替换为更高置信度的框 max_conf_idx np.argmax([d[4] for d in same_panel]) if det[4] same_panel[max_conf_idx][4]: filtered.remove(same_panel[max_conf_idx]) filtered.append(det) return filtered逻辑说明panel_map通过cv2.findContours()提取光伏板矩形轮廓生成确保每块板有唯一ID。threshold_px50对应实际距离约0.8米按Mini 3 Pro 50米高度估算避免同一故障被多次标记。5. 避坑指南这7个报错90%新手会踩第3个让答辩老师当场皱眉5.1 现象ImportError: DLL load failed while importing cv2: 找不到指定的模块原因Windows下OpenCV与Python版本不匹配或python35.dll被其他程序占用常见于Anaconda多环境。项目中python35.dll是Python 3.5专用但现代环境多用3.8。解决删除根目录下python35.dll和tk86t.dll用pip install opencv-python4.5.5.64安装兼容版而非opencv-contrib-python含冲突模块。5.2 现象RuntimeError: CUDA out of memory即使batch_size1也报错原因YOLOv5s默认加载torch.float32权重但RTX 3060显存仅12GB需强制FP16。解决在inference/pipeline.py中添加model.half() # 转为float16 img img.half() # 输入也转half且确保torch.cuda.amp.autocast()上下文管理器包裹推理过程。5.3 现象热斑检测结果在图像边缘大量误报答辩时老师指着PPT问“这是故障还是镜头污渍”原因未执行preprocess/undistort.py的黑边裁剪YOLO在畸变校正后的黑边区域产生伪影。解决必须在pipeline.py开头加入# 确保undistort返回的是裁剪后图像 img fisheye_undistort(img_path, K, D) h, w img.shape[:2] if h 1000 or w 1000: # 黑边裁剪后尺寸应接近1920×1080 raise ValueError(Undistortion failed: image too small)这是答辩翻车高发点——很多同学跳过预处理直接跑YOLO边缘伪影被模型当成热斑。5.4 现象events.out.tfevents.xxx文件无法用TensorBoard打开显示“no scalar data”原因项目用PyTorch训练但TensorBoard日志是TensorFlow格式历史遗留。这些文件是作者用TF保存的中间检查点非必需。解决直接删除events.out.tfevents.*和checkpoint文件不影响推理。训练日志在runs/train/exp/下为PyTorch格式。5.5 现象sysconfig.cfg中pythonpath指向不存在路径导致activate.bat失败原因虚拟环境路径硬编码解压后路径变更。解决用文本编辑器打开sysconfig.cfg将[install] prefix后的路径改为当前解压目录绝对路径或直接删掉该文件——项目实际不依赖此配置。6. 部署到树莓派4B的终极技巧用OpenCV-DNN替代PyTorch推理速度提升3.2倍6.1 为什么树莓派不能直接跑PyTorch模型树莓派4B的BCM2711 CPU4×Cortex-A72和VideoCore VI GPU不支持PyTorch的CUDA算子。即使装了torchmodel.forward()也会fallback到CPUYOLOv5s单图耗时12秒。我们必须用OpenCV-DNN——它调用ARM NEON指令集且支持ONNX模型。6.2 模型转换YOLOv5s → ONNX → OpenCV-DNN关键步骤在PC端执行# 1. 导出ONNXYOLOv5官方export.py修改版 python export.py --weights yolov5s.pt --include onnx --imgsz 768 --device cpu # 2. 修正ONNX输出节点YOLOv5导出的ONNX有冗余输出 # 用onnx-simplifier清理pip install onnx-simplifier python -m onnxsim yolov5s.onnx yolov5s_sim.onnx # 3. 在树莓派上加载src/deploy/rpi_inference.py import cv2 import numpy as np net cv2.dnn.readNetFromONNX(yolov5s_sim.onnx) net.setPreferableBackend(cv2.dnn.DNN_BACKEND_OPENCV) net.setPreferableTarget(cv2.dnn.DNN_TARGET_CPU) # 不用GPUCPU更稳 def detect(img): blob cv2.dnn.blobFromImage( img, 1/255.0, (768,768), (0,0,0), swapRBTrue, cropFalse ) net.setInput(blob) outputs net.forward(net.getUnconnectedOutLayersNames()) # 获取所有输出层 # 解析YOLO输出此处省略NMS用cv2.dnn.NMSBoxes boxes, confs, classes [], [], [] for output in outputs: for detection in output: scores detection[5:] class_id np.argmax(scores) confidence scores[class_id] if confidence 0.5: center_x, center_y int(detection[0]*img.shape[1]), int(detection[1]*img.shape[0]) w, h int(detection[2]*img.shape[1]), int(detection[3]*img.shape[0]) x, y int(center_x - w/2), int(center_y - h/2) boxes.append([x,y,w,h]) confs.append(float(confidence)) classes.append(class_id) indices cv2.dnn.NMSBoxes(boxes, confs, 0.5, 0.4) return [(boxes[i[0]], confs[i[0]], classes[i[0]]) for i in indices]注意cv2.dnn.NMSBoxes返回的是[[0],[1]]格式必须用i[0]索引否则报错TypeError: list indices must be integers。6.3 树莓派性能实测对比表方案硬件输入尺寸单图耗时mAP0.5PyTorch CPURPi4B 4GB640×64012.4s78.1%OpenCV-DNN CPURPi4B 4GB768×7683.8s80.3%OpenCV-DNN NEON优化RPi4B 4GB768×7681.2s81.7%NEON优化需编译OpenCV时加-DENABLE_NEONON官方预编译包不支持。我用cmake -D CMAKE_BUILD_TYPERELEASE -D CMAKE_INSTALL_PREFIX/usr/local -D ENABLE_NEONON ..编译耗时47分钟但值得。6.4 最后一道防线用geo_utils.py把像素坐标转为经纬度生成巡检报告# src/utils/geo_utils.py def pixel_to_geo(pixel_x, pixel_y, exif_data, img_shape): pixel_x,y: 检测框中心像素坐标 exif_data: 从PIL.Image._getexif()提取的GPSInfo # 步骤1根据EXIF获取无人机GPS坐标和航向角 lat_ref, lat_val exif_data.get(2, (0,0)), exif_data.get(4, (0,0)) lon_ref, lon_val exif_data.get(6, (0,0)), exif_data.get(8, (0,0)) # 步骤2计算像素对应地面距离需知道飞行高度和焦距 # Mini 3 Pro 4K模式焦距f24mm传感器宽23.5mm → 像素尺寸23.5/1920≈0.0122mm # 飞行高度h50m → 地面分辨率0.0122 * 50 / 24 ≈ 0.0254m/px ground_resolution 0.0254 # 单位米/像素 # 步骤3转为WGS84坐标简化版忽略地球曲率 delta_lat (pixel_y - img_shape[0]/2) * ground_resolution / 111320 # 1度纬度≈111320m delta_lon (pixel_x - img_shape[1]/2) * ground_resolution / (111320 * np.cos(np.radians(lat_val))) geo_lat lat_val delta_lat geo_lon lon_val delta_lon return geo_lat, geo_lon # 生成报告 detections run_inference(test.jpg) for box, conf, cls in detections: x_c (box[0] box[2]) / 2 y_c (box[1] box[3]) / 2 lat, lon pixel_to_geo(x_c, y_c, exif_data, img.shape) print(f故障类型: {cls}, 置信度: {conf:.3f}, 经纬度: {lat:.6f}, {lon:.6f})从那以后我每次部署新项目都强制走一遍pixel_to_geo的精度验证——用已知坐标的光伏板角点拍照测输出经纬度误差。误差5米就回溯预处理参数。这招让我躲过了三次现场验收翻车。希望帮到你。本文还有配套的精品资源点击获取
返回列表