Files
observer/training/clip_generate_labels.py
T

208 lines
6.4 KiB
Python

#!/usr/bin/env python3
"""
使用 CLIP 模型的滑动窗口方法检测野鸡位置
生成 YOLO 格式的标注文件
"""
import cv2
import numpy as np
import torch
import clip
from PIL import Image
from pathlib import Path
from torchvision.ops import nms
# 配置
IMAGES_DIR = Path(__file__).parent / "datasets" / "images" / "pheasant"
OUTPUT_DIR = Path(__file__).parent / "datasets" / "yolo_format"
# 检测配置
WINDOW_SIZES = [128, 256, 512] # 滑动窗口大小(增大)
STRIDE_RATIO = 0.7 # 窗口滑动步长比例(增大)
CONFIDENCE_THRESHOLD = 0.5 # 置信度阈值(提高)
NMS_THRESHOLD = 0.5 # NMS 阈值(提高)
# 野鸡的文本描述
PHEASANT_PROMPTS = [
"a photo of a pheasant in the wild",
"a wild pheasant in natural habitat",
"a bird with colorful feathers in grass",
]
class CLIPDetector:
def __init__(self):
print("加载 CLIP 模型...")
self.model, self.preprocess = clip.load("ViT-B/32", device="cpu")
self.model.eval()
print("CLIP 模型加载完成")
# 预计算文本特征
print("预计算文本特征...")
text_tokens = clip.tokenize(PHEASANT_PROMPTS).to("cpu")
with torch.no_grad():
self.text_features = self.model.encode_text(text_tokens)
self.text_features /= self.text_features.norm(dim=-1, keepdim=True)
def classify_region(self, image_region):
"""对图像区域进行分类"""
# 转换为 PIL Image
if isinstance(image_region, np.ndarray):
image_region = Image.fromarray(cv2.cvtColor(image_region, cv2.COLOR_BGR2RGB))
# 预处理
image_input = self.preprocess(image_region).unsqueeze(0).to("cpu")
# 计算图像特征
with torch.no_grad():
image_features = self.model.encode_image(image_input)
image_features /= image_features.norm(dim=-1, keepdim=True)
# 计算相似度
similarity = (100.0 * image_features @ self.text_features.T).softmax(dim=-1)
# 返回最高分
return float(similarity[0].max())
def detect(self, image):
"""检测图片中的野鸡"""
h, w = image.shape[:2]
detections = []
# 多尺度滑动窗口
for window_size in WINDOW_SIZES:
stride = int(window_size * STRIDE_RATIO)
# 滑动窗口
for y in range(0, h - window_size, stride):
for x in range(0, w - window_size, stride):
# 提取窗口区域
region = image[y:y+window_size, x:x+window_size]
# 分类
score = self.classify_region(region)
# 如果置信度足够高,保存检测结果
if score > CONFIDENCE_THRESHOLD:
detections.append({
'bbox': [x, y, x + window_size, y + window_size],
'score': score,
})
# NMS 去重
if len(detections) > 0:
detections = self.nms(detections)
return detections
def nms(self, detections):
"""非极大值抑制"""
if len(detections) == 0:
return []
# 转换为 torch 格式
boxes = torch.tensor([d['bbox'] for d in detections], dtype=torch.float32)
scores = torch.tensor([d['score'] for d in detections], dtype=torch.float32)
# 应用 NMS
keep_indices = nms(boxes, scores, NMS_THRESHOLD)
# 保留 NMS 后的检测结果
filtered_detections = [detections[i] for i in keep_indices]
return filtered_detections
def convert_to_yolo_format(bbox, img_width, img_height):
"""将边界框转换为 YOLO 格式 (cx, cy, w, h)"""
x1, y1, x2, y2 = bbox
cx = (x1 + x2) / 2 / img_width
cy = (y1 + y2) / 2 / img_height
w = (x2 - x1) / img_width
h = (y2 - y1) / img_height
return cx, cy, w, h
def main():
# 初始化检测器
detector = CLIPDetector()
# 创建输出目录
(OUTPUT_DIR / "images" / "train").mkdir(parents=True, exist_ok=True)
(OUTPUT_DIR / "labels" / "train").mkdir(parents=True, exist_ok=True)
# 获取所有图片
image_extensions = {".jpg", ".jpeg", ".png", ".bmp"}
image_files = [
f for f in IMAGES_DIR.iterdir()
if f.suffix.lower() in image_extensions
]
print(f"找到 {len(image_files)} 张图片")
# 处理每张图片
labeled_count = 0
for img_path in image_files:
print(f"\n处理: {img_path.name}")
# 读取图片
image = cv2.imread(str(img_path))
if image is None:
print(f" 跳过: 无法读取 {img_path.name}")
continue
img_height, img_width = image.shape[:2]
# 检测野鸡
detections = detector.detect(image)
# 生成标注
labels = []
for det in detections:
bbox = det['bbox']
score = det['score']
# 转换为 YOLO 格式
cx, cy, w, h = convert_to_yolo_format(bbox, img_width, img_height)
labels.append(f"0 {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}\n")
# 在图片上绘制检测框(用于可视化)
x1, y1, x2, y2 = bbox
cv2.rectangle(image, (int(x1), int(y1)), (int(x2), int(y2)), (0, 255, 0), 2)
cv2.putText(image, f"pheasant: {score:.2f}", (int(x1), int(y1) - 10),
cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2)
# 保存标注文件
if len(labels) > 0:
labeled_count += 1
# 保存带标注的图片(用于可视化)
cv2.imwrite(str(OUTPUT_DIR / "images" / "train" / img_path.name), image)
# 保存标注
label_path = OUTPUT_DIR / "labels" / "train" / (img_path.stem + ".txt")
with open(label_path, "w") as f:
f.writelines(labels)
print(f" ✓ 标注了 {len(labels)} 个目标")
else:
print(f" - 未检测到野鸡")
print(f"\n完成!")
print(f" 总图片数: {len(image_files)}")
print(f" 有效标注: {labeled_count}")
print(f" 输出目录: {OUTPUT_DIR}")
# 创建 data.yaml
data_yaml = OUTPUT_DIR / "data.yaml"
with open(data_yaml, "w") as f:
f.write(f"""# Observer 数据集配置 - 只识别野鸡
path: {OUTPUT_DIR}
train: images/train
val: images/train
# 类别
nc: 1
names: ['pheasant']
""")
print(f" 数据配置: {data_yaml}")
if __name__ == "__main__":
main()