# powerline **Repository Path**: chris118/powerline ## Basic Information - **Project Name**: powerline - **Description**: No description available - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-06-13 - **Last Updated**: 2026-06-14 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 高压导线边缘检测算法 从人工刺点出发,沿指定方向自动搜索高压导线(高压线)的灰色边缘,输出边缘点坐标及可视化结果。 --- ## 目录 - [背景与需求](#背景与需求) - [算法原理](#算法原理) - [刺点搜索方向规则](#刺点搜索方向规则) - [代码结构](#代码结构) - [使用方法](#使用方法) - [输出文件说明](#输出文件说明) - [参数调优指南](#参数调优指南) - [调试与诊断](#调试与诊断) --- ## 背景与需求 电力巡检中,需要通过图像识别高压导线的精确位置。人工在图像上刺点(标记点)作为搜索起点,算法从刺点出发,沿导线方向搜索,自动定位导线的边缘位置。 **核心挑战:** - 导线是灰色的,与天空背景对比度低 - 水平方向(离镜头近)导线清晰,垂直方向(离镜头远)导线较淡 - 图像中可能有多条导线,需要避免误选远处导线 - 尽量减少硬编码阈值,提高算法鲁棒性 --- ## 算法原理 ### 总体思路(方案A:半幅过渡点法) 算法分为四个步骤: ``` 刺点(人工标记) ↓ ① 沿搜索方向采样灰度剖面 ↓ ② 找导线中心(最近显著暗谷) ↓ ③ 找导线边缘(30%阈值半幅过渡点法) ↓ ④ 亚像素精化(抛物线拟合) ↓ 输出边缘坐标 + 可视化 ``` --- ### 步骤①:采样灰度剖面 从刺点出发,沿搜索方向逐像素采样灰度值。 - 使用 **3×3 局部窗口平均**,抗噪并提高鲁棒性 - 采样长度默认 300 像素(可通过 `max_steps` 参数调整) - 输出:灰度值数组 `profile[]`,以及每个采样点的位置坐标 ``` profile[] = [ sky_pixel, sky_pixel, ..., wire_edge, wire_center, ... ] ↑ 起点(刺点) ↑ 搜索方向 ``` --- ### 步骤②:找导线中心(最近显著暗谷法) **目标:** 在灰度剖面中找到导线中心(最暗的位置)。 **为什么不用全局最暗点?** 在多导线场景中,远处可能有更粗/更暗的导线。但刺点标记的是近处的目标导线,应该优先找**离刺点最近**的导线。 **算法:** 1. 对剖面做高斯平滑(`GaussianBlur`,核大小5) 2. 计算天空基线:剖面起始段(前10像素或1/4长度)的灰度均值 3. 找出所有**显著暗谷**: - 暗谷定义:局部最小值(`profile[i] < profile[i-1]` 且 `profile[i] < profile[i+1]`) - 显著条件:比天空基线暗至少 `min_depth=8` 个灰度值 4. **优先选择离起点最近的显著暗谷** 5. 额外验证:如果最近谷的深度不到最深谷的 40%,且最深谷也在 100 像素以内,则改用最深谷(防止近处是噪声误判) ``` 剖面灰度示例: sky: ~180 ──────────────────────── wire: ↘ ____ ↙ ← 暗谷(局部最小值) | | sky center wire ← 优先选最近的谷 ``` --- ### 步骤③:找导线边缘(30%阈值半幅过渡点法) **目标:** 在"刺点 → 导线中心"之间,找到导线与天空的交界处(边缘)。 **为什么不用最陡梯度法?** 梯度最陡处总是在导线谷底的斜坡上(靠近中心),而不是导线的真实外边缘。 ``` 灰度剖面示意图(从天空到导线中心): 灰度值 200 ┤ ● 刺点(天空) │ ╱ 180 ┤ ╱ │ ╱ 160 ┤ ╱ │ ╱ 140 ┤ ╱ │ ╱ 120 ┤ ● ← 30%阈值点(边缘)✅ 这里才是导线外沿 │ ╱ 100 ┤● ← 最陡梯度处(谷底斜坡)❌ 不在边缘 │ 80 ┤ ● ← 导线中心(最暗点) └───┬───┬───┬───┬───→ 搜索步数 边缘 最陡 中心 天空基线: 180 导线中心: 80 30%阈值: 180 - 0.30 × (180 - 80) = 150 ``` **算法:** 1. 取"起始点 → 导线中心"之间的剖面子区间 2. 计算天空基线 `sky_level`(子区间起始段均值) 3. 计算导线中心值 `wire_val`(子区间内最小值) 4. 计算边缘阈值: ``` threshold = sky_level - 0.30 × (sky_level - wire_val) ``` - 系数 `0.30` 表示:从天空向导线方向,亮度下降 30% 处认定为边缘 - 该系数可通过 `EDGE_THRESHOLD_RATIO` 参数调整 5. 从天空侧向中心方向,找到**第一个灰度值 ≤ 阈值**的像素,作为边缘点 6. 限制:边缘点至少离中心 2 像素以上(防止误判到中心位置) **为什么是 30%?** 导线从天空到中心的灰度下降通常是陡峭的,30% 处恰好落在导线的上沿/外沿位置。若需要边缘点更靠外(更靠近天空),可减小该系数(如 0.20);若需要更靠内(更靠近中心),可增大该系数(如 0.50)。 --- ### 步骤④:亚像素精化 边缘点的整数像素坐标精度有限,通过抛物线拟合梯度在边缘点附近的变化,实现亚像素级精度。 **方法:** 在边缘点前后各取一个梯度值,用抛物线 `y = ax² + bx + c` 拟合,求抛物线极小值处的偏移量。 - 偏移范围限制在 ±0.5 像素内,防止过拟合 - 输出:亚像素级边缘坐标(浮点数精度) --- ## 刺点搜索方向规则 ### 18点模式(每导线9点) | 点编号 | 所属导线 | 搜索方向 | 说明 | |--------|----------|----------|------| | 1~4 | 左导线 | 垂直向上 | 从导线下方向上方搜索 | | 5~9 | 左导线 | 水平向右 | 从左向右搜索 | | 10~13 | 右导线 | 垂直向上 | 从导线下方向上搜索 | | 14~18 | 右导线 | 水平向左 | 从右向左搜索 | ### 22点模式(每导线11点) | 点编号 | 所属导线 | 搜索方向 | 说明 | |--------|----------|----------|------| | 1~5 | 左导线 | 垂直向上 | 从导线下方向上搜索 | | 6~11 | 左导线 | 水平向右 | 从左向右搜索 | | 12~16 | 右导线 | 垂直向上 | 从导线下方向上搜索 | | 17~22 | 右导线 | 水平向左 | 从右向左搜索 | ### 通用模式 当刺点数量不是 18 或 22 时,自动平分前后两半,前一半为左导线,后一半为右导线;垂直/水平搜索点按中点自动划分。 --- ## 代码结构 ``` wire_edge_detector.py ├── imread_rgb() # 中文路径兼容的图像读取 ├── WireEdgeDetector 类 │ ├── __init__() # 初始化,加载图像和刺点 │ ├── _load_device_points() # 读取刺点坐标文件 │ ├── determine_search_direction() # 判断每个点的搜索方向 │ ├── _sample_along_direction() # 沿方向采样灰度剖面 │ ├── _find_wire_center() # 找导线中心(最近暗谷法) │ ├── _find_edge_from_center() # 找边缘(30%阈值法) │ ├── _subpixel_refine() # 亚像素精化 │ ├── _detect_edge() # 核心检测流程(串联上述步骤) │ ├── _subpixel_to_coord() # 亚像素索引转坐标 │ ├── search_edge() # 单点边缘搜索入口 │ ├── process_all_points() # 批量处理所有刺点 │ ├── visualize_results() # 可视化并保存结果图 │ ├── save_coordinates() # 保存坐标到CSV │ └── _save_debug_profiles() # 保存调试剖面图 ├── process_station() # 处理单个站点 └── main() # 批量处理所有站点 ``` --- ## 使用方法 ### 环境依赖 ```bash pip install opencv-python numpy pillow matplotlib ``` 或使用项目虚拟环境(推荐): ```bash # 使用 WorkBuddy 管理的 Python 环境 C:\Users\Administrator\.workbuddy\binaries\python\envs\default\bin\python wire_edge_detector.py ``` ### 数据准备 每个站点的数据按如下结构放置: ``` data/ └── <站点名称>/ ├── imgs/ │ └── 001_m.jpg # 高压线图像(4000×3000) └── device_m.txt # 刺点坐标文件(每行 "x,y") ``` **刺点文件格式示例(`device_m.txt`):** ``` 1200,2500 1250,2400 ... ``` - 每行一个点,格式为 `x,y`(逗号分隔,无空格) - 前一半点为左导线,后一半点为右导线 - 支持 18 点模式(每导线9点)和 22 点模式(每导线11点) ### 运行 ```bash python wire_edge_detector.py ``` 程序会自动扫描 `data/` 目录下所有有效站点,逐个处理并输出结果。 ### 自定义参数 在 `WireEdgeDetector.__init__()` 中可调整: ```python # _sample_along_direction 中的采样参数 max_steps = 300 # 最大搜索步数(像素) window_size = 1 # 局部平均窗口半径(1=3×3窗口) # _find_wire_center 中的暗谷检测参数 min_depth = 8 # 暗谷至少比天空暗多少个灰度值 depth_ratio = 0.4 # 最近谷深度不及最深谷40%时,考虑用最深谷 max_depth_dist = 100 # 最深谷在此距离内才考虑切换 # _find_edge_from_center 中的边缘阈值参数 EDGE_THRESHOLD_RATIO = 0.30 # 边缘阈值系数(天空→中心的30%处) ``` --- ## 输出文件说明 每个站点的 `output/` 目录下生成以下文件: ### 1. `result_marked.jpg` — 可视化结果图 | 标记 | 颜色 | 含义 | |------|------|------| | ● | 蓝色 | 原始刺点位置 | | ✛ | 红色 | 检测到的导线边缘点(1像素圆点+3像素十字) | | ─── | 绿色 | 边缘点连线(导线轨迹) | | 数字 | 黑/红色 | 点编号(1~N) | ### 2. `edge_coordinates.csv` — 边缘坐标数据 ```csv point_index,orig_x,orig_y,edge_x,edge_y,delta_x,delta_y,subpixel_x,subpixel_y,subpixel_dx,subpixel_dy 1,1200,2500,1210,2480,10,-20,1210.35,2479.82,10.35,-20.18 ... ``` | 字段 | 含义 | |------|------| | `point_index` | 点编号(从1开始) | | `orig_x, orig_y` | 原始刺点坐标(整数) | | `edge_x, edge_y` | 检测到的边缘点坐标(整数) | | `delta_x, delta_y` | 刺点到边缘点的偏移量(整数) | | `subpixel_x, subpixel_y` | 亚像素级边缘坐标(浮点数) | | `subpixel_dx, subpixel_dy` | 亚像素级偏移量(浮点数) | ### 3. `profile_XX.png` — 调试剖面图(每个刺点一张) 每张图显示: - **蓝色曲线**:灰度剖面值 - **绿色虚线**:检测到的导线中心位置 - **红色竖线**:检测到的边缘位置 - **橙色虚线**:边缘阈值线(`threshold` 值) - **青色虚线**:天空基线(`sky_mean` 值) 通过剖面图可以直观判断: - 导线中心是否选对(绿色虚线是否在最暗处) - 边缘点是否合理(红色竖线是否在阈值线附近) - 是否需要调整 `EDGE_THRESHOLD_RATIO` 或 `min_depth` 参数 --- ## 参数调优指南 ### 边缘点偏内(太靠近导线中心) → **增大** `EDGE_THRESHOLD_RATIO`(如 0.30 → 0.50) 边缘阈值更接近导线中心值,过渡点会更靠内。 ### 边缘点偏外(太靠近天空) → **减小** `EDGE_THRESHOLD_RATIO`(如 0.30 → 0.15) 边缘阈值更接近天空基线,过渡点会更靠外。 ### 多导线场景中选错了导线 → 检查 `_find_wire_center` 中的 `depth_ratio` 和 `max_depth_dist` 参数 - `depth_ratio=0.4`:最近谷深度不及最深谷40%时才考虑切换 - `max_depth_dist=100`:最深谷超过100像素就不切换 - 可根据实际场景调整这两个值 ### 垂直方向(远处、导线淡)检测不稳定 → 减小 `min_depth`(如 8 → 5),让更浅的暗谷也能被识别 远处导线比近处导线淡,灰度下降幅度小,需要降低显著暗谷的深度阈值。 ### 水平方向(近处、导线清晰)误检 → 增大 `min_depth`(如 8 → 12),过滤噪声造成的虚假暗谷 --- ## 调试与诊断 ### 查看单点剖面图 ```python # 在 Python 中交互式查看某个点的剖面 import matplotlib.pyplot as plt import numpy as np detector = WireEdgeDetector(img_path, device_path) profile, positions = detector._sample_along_direction(detector.points[0], 'vertical_up') plt.plot(profile) plt.axhline(y=np.mean(profile[:10]), color='cyan', linestyle=':') # 天空基线 plt.show() ``` ### 常见原因分析 | 现象 | 可能原因 | 解决方案 | |------|----------|----------| | 边缘点在天空上(远离导线) | 30%阈值太高,误判噪声为边缘 | 减小 `EDGE_THRESHOLD_RATIO` | | 边缘点在导线中心上 | 30%阈值太低,或 `max_edge` 限制太严 | 增大 `EDGE_THRESHOLD_RATIO` 或放宽 `max_edge` | | 导线中心选到了远处导线 | 最近暗谷比远处暗谷浅太多 | 调整 `depth_ratio` 或 `max_depth_dist` | | 完全没找到边缘(返回刺点位置) | `min_depth` 太高,远处导线不满足 | 减小 `min_depth` | | 红色十字和蓝色原点几乎重合 | 没检测到导线(剖面太平坦) | 检查刺点是否在导线上,或增大 `max_steps` | --- ## 算法演进历史 | 版本 | 方法 | 问题 | |------|------|------| | v1 | 最大梯度法 | 梯度最陡处在谷底斜坡,不在边缘 | | v2 | 对比度法(天空均值 - 局部均值) | 对远处淡导线不敏感 | | v3(方案A初版) | 导线中心 + 最陡梯度下降法 | 边缘点偏内,不在真实边缘 | | **v4(当前版)** | **最近暗谷 + 30%阈值半幅过渡点法** | **用户验证通过** ✅ | --- ## 作者与维护 - 开发:WorkBuddy AI Assistant - 最后更新:2026-06-12 - 项目路径:`D:\code\llm\workbuddy\powerline\`