7 Commits

Author SHA1 Message Date
SkyLi 706bd63b62 chore: adjust pyproject.toml comment positions 2026-03-29 00:50:34 +08:00
SkyLi c0c58b307e chore: bump version to v0.2.0 2026-03-29 00:45:42 +08:00
SkyLi 8813cdf6f1 refactor: decouple TempFeatureExtractor from detection logic
- Remove detect_and_extract() method from TempFeatureExtractor
  (moved to FaultDiagnoser pipeline)
- Remove YOLO model loading from TempFeatureExtractor.__init__
- Remove duplicate train_yolo_model/validate_model from detect.py
  (already exist in scripts/train.py)
- Fix usage_example.py to use new import path and TempFeatureExtractor API
2026-03-29 00:33:11 +08:00
SkyLi 426143099a fix: restructure src layout to proper package directory
Move src/__init__.py, detect.py, diagnose.py, extract_temp.py, utils/
into src/infrared_data/ to fix wheel package structure. Previously the
whl placed files at root instead of infrared_data/ directory, causing
ModuleNotFoundError on install.

Also update pyproject.toml packages.find config to use standard
src-layout discovery.
2026-03-29 00:29:47 +08:00
SkyLi 5099b483d3 feat: export FaultDiagnoser, InfraredDetector, TempFeatureExtractor
Add public API exports in infrared_data package __init__.py.
2026-03-29 00:29:47 +08:00
SkyLi 8e3206eb4f docs: remove obsolete dataset download scripts from README 2026-03-29 00:29:47 +08:00
SkyLi 70892a17f9 chore: ignore src_docs and CLAUDE.md in docs directory 2026-03-29 00:24:30 +08:00
12 changed files with 147 additions and 334 deletions
+5
View File
@@ -5,6 +5,7 @@ __pycache__/
dist/ dist/
build/ build/
*.egg *.egg
*.whl
# PyTorch # PyTorch
*.pth *.pth
@@ -62,3 +63,7 @@ uv.lock
tmp/ tmp/
temp/ temp/
*.tmp *.tmp
# Docs
src_docs/
CLAUDE.md
-63
View File
@@ -404,21 +404,6 @@ uv run python scripts/train.py export \
--format onnx --format onnx
``` ```
### 下载数据集
```bash
uv run python scripts/download_dataset.py \
--urls-file data/dataurls.txt \
--output-dir data/raw/images
```
### 重试下载失败文件
```bash
uv run python scripts/retry_download.py \
--urls-file data/dataurls.txt \
--output-dir data/raw/images \
--wait 60
```
### 分析ROI温度特征 ### 分析ROI温度特征
```bash ```bash
uv run python scripts/analyze_temp_features.py \ uv run python scripts/analyze_temp_features.py \
@@ -429,51 +414,6 @@ uv run python scripts/analyze_temp_features.py \
--- ---
## 开发流程
### 阶段一:项目初始化与环境配置
1. 使用uv初始化项目
2. 配置PyTorch CUDA环境
3. 安装ultralytics YOLO框架
4. 验证GPU环境可用性
### 阶段二:数据集准备
1. 下载SCIDB红外图像数据集
2. 按类别组织图像目录
3. 转换为YOLO格式
4. 创建dataset.yaml配置文件
### 阶段三:模型训练
1. 选择YOLOv8n作为基础模型
2. 配置训练参数
3. 启动GPU训练
4. 监控训练指标 (mAP, Precision, Recall)
5. EarlyStopping防止过拟合
### 阶段四:ROI温度特征提取
1. 实现温度统计特征 (max/min/mean/std/median)
2. 实现温度直方图特征
3. 实现温度梯度特征
### 阶段五:故障诊断Pipeline
1. 整合检测+温度提取
2. 定义故障分类规则
3. 实现批量诊断功能
4. 输出结构化JSON结果
### 阶段六:测试与验证
1. 端到端Pipeline测试
2. 批量图像诊断测试
3. 性能指标验证
---
## 技术栈 ## 技术栈
| 组件 | 技术/版本 | | 组件 | 技术/版本 |
@@ -534,9 +474,6 @@ names: ['bushing', 'arrester', 'breaker', 'clamp', 'conservator',
### Q: 训练时GPU内存不足 ### Q: 训练时GPU内存不足
A: 减小batch大小或使用更小的模型 (--batch 8 --model-size n) A: 减小batch大小或使用更小的模型 (--batch 8 --model-size n)
### Q: 下载失败429错误
A: 使用retry_download.py脚本,设置更长的等待时间 (--wait 120)
### Q: 如何使用自己的数据集 ### Q: 如何使用自己的数据集
A: A:
1. 将YOLO格式数据集放入data/目录 1. 将YOLO格式数据集放入data/目录
+5 -1
View File
@@ -1,6 +1,6 @@
[project] [project]
name = "infrared-data" name = "infrared-data"
version = "0.1.0" version = "0.2.0"
description = "Infrared Equipment Detection based on YOLO - New Dataset" description = "Infrared Equipment Detection based on YOLO - New Dataset"
readme = "README.md" readme = "README.md"
requires-python = ">=3.10" requires-python = ">=3.10"
@@ -18,6 +18,10 @@ dependencies = [
"ultralytics>=8.4.27", "ultralytics>=8.4.27",
] ]
[tool.setuptools.packages]
find = {where = ["src"]}
# PyTorch CUDA版本配置 # PyTorch CUDA版本配置
[tool.uv] [tool.uv]
# 指定PyTorch使用CUDA索引 # 指定PyTorch使用CUDA索引
+2 -4
View File
@@ -8,9 +8,7 @@ from pathlib import Path
# 添加项目根目录到路径 # 添加项目根目录到路径
sys.path.insert(0, str(Path(__file__).parent.parent)) sys.path.insert(0, str(Path(__file__).parent.parent))
from src.detect import InfraredDetector from src.infrared_data import InfraredDetector, TempFeatureExtractor, FaultDiagnoser
from src.extract_temp import TempFeatureExtractor
from src.diagnose import FaultDiagnoser
import cv2 import cv2
import json import json
@@ -48,7 +46,7 @@ def example3_temperature_extraction():
"""示例3:温度特征提取""" """示例3:温度特征提取"""
print("\n【示例3】温度特征提取") print("\n【示例3】温度特征提取")
extractor = TempFeatureExtractor("weights/best.pt") extractor = TempFeatureExtractor()
detector = InfraredDetector("weights/best.pt") detector = InfraredDetector("weights/best.pt")
# 读取图像 # 读取图像
-1
View File
@@ -1 +0,0 @@
# Infrared Fault Detection Package
-189
View File
@@ -1,189 +0,0 @@
"""
ROI温度特征提取模块
基于YOLO检测结果,提取ROI区域的温度特征
"""
import numpy as np
import cv2
import json
from pathlib import Path
from typing import Dict, List, Optional, Union
from ultralytics import YOLO
from .utils.temp_utils import (
extract_temp_stats,
extract_temp_histogram,
calculate_temp_gradient,
normalize_roi
)
class TempFeatureExtractor:
"""红外图像ROI温度特征提取器"""
def __init__(self, model_path: Optional[str] = None):
"""
初始化温度特征提取器
Args:
model_path: YOLO模型路径,如果为None则只提取温度特征
"""
self.model = None
if model_path and Path(model_path).exists():
self.model = YOLO(model_path)
def detect_and_extract(self, image: np.ndarray,
conf: float = 0.25,
device: Optional[str] = None) -> List[Dict]:
"""
对图像进行检测并提取ROI温度特征
Args:
image: 输入红外图像
conf: 检测置信度阈值
device: 运行设备,例如 '0' 表示GPU 0,'cpu' 表示CPU
Returns:
检测结果列表,每个元素包含检测框和温度特征
"""
if self.model is None:
raise ValueError("Model not loaded. Please provide a valid model path.")
results = self.model(image, conf=conf, device=device)
detections = []
for result in results:
boxes = result.boxes
if boxes is None:
continue
for box in boxes:
xyxy = box.xyxy[0].cpu().numpy()
conf_score = float(box.conf[0])
cls_id = int(box.cls[0])
x1, y1, x2, y2 = map(int, xyxy)
roi = image[y1:y2, x1:x2]
temp_features = extract_temp_stats(roi)
temp_features.update({
'bbox': [int(x1), int(y1), int(x2), int(y2)],
'confidence': conf_score,
'class_id': cls_id,
'class_name': result.names[cls_id]
})
detections.append(temp_features)
return detections
def extract_from_roi(self, roi: np.ndarray,
include_hist: bool = True,
include_gradient: bool = True) -> Dict:
"""
从指定ROI提取温度特征
Args:
roi: ROI区域图像
include_hist: 是否包含直方图特征
include_gradient: 是否包含梯度特征
Returns:
温度特征字典
"""
features = extract_temp_stats(roi)
if include_hist:
features['histogram'] = extract_temp_histogram(roi).tolist()
if include_gradient:
features.update(calculate_temp_gradient(roi))
return features
def extract_from_bbox(self, image: np.ndarray,
bbox: List[int]) -> Dict:
"""
从给定边界框提取温度特征
Args:
image: 输入图像
bbox: [x1, y1, x2, y2] 边界框坐标
Returns:
温度特征字典
"""
x1, y1, x2, y2 = bbox
roi = image[y1:y2, x1:x2]
return self.extract_from_roi(roi)
def load_infrared_image(image_path: str) -> np.ndarray:
"""
加载红外图像
Args:
image_path: 图像路径
Returns:
图像数组
"""
image = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE)
if image is None:
raise ValueError(f"Failed to load image: {image_path}")
return image
def batch_extract_features(image_dir: Union[str, Path],
output_path: Union[str, Path],
model_path: Optional[str] = None,
conf: float = 0.25,
device: Optional[str] = None) -> None:
"""
批量处理图像并提取温度特征
Args:
image_dir: 图像目录路径
output_path: 输出JSON文件路径
model_path: YOLO模型路径
conf: 检测置信度阈值
device: 运行设备,例如 '0' 表示GPU 0,'cpu' 表示CPU
"""
image_dir = Path(image_dir)
extractor = TempFeatureExtractor(model_path)
all_results = []
for img_path in sorted(image_dir.glob('*')):
if img_path.suffix.lower() not in ['.jpg', '.jpeg', '.png', '.bmp', '.tiff']:
continue
try:
image = load_infrared_image(str(img_path))
detections = extractor.detect_and_extract(image, conf=conf, device=device)
all_results.append({
'image': img_path.name,
'detections': detections
})
except Exception as e:
print(f"Error processing {img_path.name}: {e}")
continue
with open(output_path, 'w', encoding='utf-8') as f:
json.dump(all_results, f, indent=2, ensure_ascii=False)
print(f"Processed {len(all_results)} images, results saved to {output_path}")
if __name__ == '__main__':
import argparse
parser = argparse.ArgumentParser(description='Extract temperature features from infrared images')
parser.add_argument('--image-dir', type=str, required=True, help='Input image directory')
parser.add_argument('--output', type=str, required=True, help='Output JSON path')
parser.add_argument('--model', type=str, default=None, help='YOLO model path')
parser.add_argument('--conf', type=float, default=0.25, help='Detection confidence threshold')
parser.add_argument('--device', type=str, default=None, help='Device (e.g., "0" for GPU, "cpu" for CPU)')
args = parser.parse_args()
batch_extract_features(args.image_dir, args.output, args.model, args.conf, args.device)
+6
View File
@@ -0,0 +1,6 @@
# Infrared Fault Detection Package
from .diagnose import FaultDiagnoser
from .detect import InfraredDetector
from .extract_temp import TempFeatureExtractor
__all__ = ['FaultDiagnoser', 'InfraredDetector', 'TempFeatureExtractor']
@@ -3,7 +3,6 @@ YOLO目标检测脚本
用于红外图像中的目标检测 用于红外图像中的目标检测
""" """
import os
from pathlib import Path from pathlib import Path
from typing import Optional, List, Dict, Union from typing import Optional, List, Dict, Union
import numpy as np import numpy as np
@@ -117,66 +116,6 @@ class InfraredDetector:
return img, len(detections) return img, len(detections)
def train_yolo_model(data_yaml: str,
model_size: str = 'n',
epochs: int = 100,
imgsz: int = 640,
batch: int = 16,
output_dir: str = 'models/train') -> str:
"""
训练YOLO模型
Args:
data_yaml: 数据集配置文件路径
model_size: 模型大小 ('n', 's', 'm', 'l', 'x')
epochs: 训练轮数
imgsz: 输入图像大小
batch: 批量大小
output_dir: 输出目录
Returns:
最佳模型路径
"""
model = YOLO(f'yolov8{model_size}.pt')
results = model.train(
data=data_yaml,
epochs=epochs,
imgsz=imgsz,
batch=batch,
project=output_dir,
name='infrared_fault',
exist_ok=True,
patience=20,
save=True,
plots=True
)
best_model_path = f"{output_dir}/infrared_fault/weights/best.pt"
return best_model_path
def validate_model(model_path: str, data_yaml: str) -> Dict:
"""
验证模型性能
Args:
model_path: 模型路径
data_yaml: 数据集配置路径
Returns:
验证指标
"""
model = YOLO(model_path)
metrics = model.val(data=data_yaml)
return {
'mAP50': float(metrics.box.map50),
'mAP50-95': float(metrics.box.map),
'precision': float(metrics.box.mp),
'recall': float(metrics.box.mr)
}
if __name__ == '__main__': if __name__ == '__main__':
import argparse import argparse
@@ -191,23 +130,9 @@ if __name__ == '__main__':
detect_parser.add_argument('--conf', type=float, default=0.25, help='Confidence threshold') detect_parser.add_argument('--conf', type=float, default=0.25, help='Confidence threshold')
detect_parser.add_argument('--device', type=str, default=None, help='Device (e.g., "0" for GPU, "cpu" for CPU)') detect_parser.add_argument('--device', type=str, default=None, help='Device (e.g., "0" for GPU, "cpu" for CPU)')
# Train command
train_parser = subparsers.add_parser('train', help='Train YOLO model')
train_parser.add_argument('--data', type=str, required=True, help='Data yaml path')
train_parser.add_argument('--model-size', type=str, default='n', choices=['n', 's', 'm', 'l', 'x'])
train_parser.add_argument('--epochs', type=int, default=100)
train_parser.add_argument('--imgsz', type=int, default=640)
train_parser.add_argument('--batch', type=int, default=16)
args = parser.parse_args() args = parser.parse_args()
if args.command == 'detect': if args.command == 'detect':
detector = InfraredDetector(args.model, args.conf) detector = InfraredDetector(args.model, args.conf)
img, n_detections = detector.detect_and_draw(args.image, args.output, device=args.device) img, n_detections = detector.detect_and_draw(args.image, args.output, device=args.device)
print(f"Detected {n_detections} objects") print(f"Detected {n_detections} objects")
elif args.command == 'train':
best_path = train_yolo_model(
args.data, args.model_size, args.epochs, args.imgsz, args.batch
)
print(f"Training complete. Best model: {best_path}")
@@ -26,7 +26,7 @@ class FaultDiagnoser:
temp_threshold: 温度阈值配置 temp_threshold: 温度阈值配置
""" """
self.detector = InfraredDetector(model_path) if model_path else None self.detector = InfraredDetector(model_path) if model_path else None
self.extractor = TempFeatureExtractor(model_path) self.extractor = TempFeatureExtractor()
self.temp_threshold = temp_threshold or {} self.temp_threshold = temp_threshold or {}
def diagnose(self, image: Union[str, np.ndarray], def diagnose(self, image: Union[str, np.ndarray],
+128
View File
@@ -0,0 +1,128 @@
"""
ROI温度特征提取模块
基于YOLO检测结果,提取ROI区域的温度特征
"""
import json
from pathlib import Path
from typing import Dict, List, Union
import cv2
import numpy as np
from .utils.temp_utils import (
calculate_temp_gradient,
extract_temp_histogram,
extract_temp_stats,
)
class TempFeatureExtractor:
"""红外图像ROI温度特征提取器"""
def extract_from_roi(
self, roi: np.ndarray, include_hist: bool = True, include_gradient: bool = True
) -> Dict:
"""
从指定ROI提取温度特征
Args:
roi: ROI区域图像
include_hist: 是否包含直方图特征
include_gradient: 是否包含梯度特征
Returns:
温度特征字典
"""
features = extract_temp_stats(roi)
if include_hist:
features["histogram"] = extract_temp_histogram(roi).tolist()
if include_gradient:
features.update(calculate_temp_gradient(roi))
return features
def extract_from_bbox(self, image: np.ndarray, bbox: List[int]) -> Dict:
"""
从给定边界框提取温度特征
Args:
image: 输入图像
bbox: [x1, y1, x2, y2] 边界框坐标
Returns:
温度特征字典
"""
x1, y1, x2, y2 = bbox
roi = image[y1:y2, x1:x2]
return self.extract_from_roi(roi)
def load_infrared_image(image_path: str) -> np.ndarray:
"""
加载红外图像
Args:
image_path: 图像路径
Returns:
图像数组
"""
image = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE)
if image is None:
raise ValueError(f"Failed to load image: {image_path}")
return image
def batch_extract_from_detections(
image_dir: Union[str, Path],
detections_list: List[Dict],
output_path: Union[str, Path],
) -> None:
"""
批量处理已检测的结果,提取温度特征
Args:
image_dir: 图像目录路径
detections_list: 检测结果列表
output_path: 输出JSON文件路径
"""
image_dir = Path(image_dir)
extractor = TempFeatureExtractor()
all_results = []
for det_result in detections_list:
img_name = det_result.get("image", "")
img_path = image_dir / img_name
if not img_path.exists():
continue
try:
image = load_infrared_image(str(img_path))
for det in det_result.get("detections", []):
bbox = det.get("bbox")
if bbox is None:
continue
x1, y1, x2, y2 = bbox
roi = image[y1:y2, x1:x2]
temp_features = extractor.extract_from_roi(roi)
temp_features.update(
{
"bbox": bbox,
"class_name": det.get("class_name"),
"confidence": det.get("confidence"),
}
)
all_results.append(
{"image": img_name, "detections": det_result.get("detections", [])}
)
except Exception as e:
print(f"Error processing {img_name}: {e}")
continue
with open(output_path, "w", encoding="utf-8") as f:
json.dump(all_results, f, indent=2, ensure_ascii=False)
print(f"Processed {len(all_results)} images, results saved to {output_path}")