File size: 7,156 Bytes
6a5bb7e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
"""Phase 1: 半监督学习 + 主动学习 — 立即可执行方案

目标: 利用 3000 张未标注图,提升 mAP 到 0.65+

Step 1: 用最佳模型生成伪标签
Step 2: 主动学习筛选高价值样本
Step 3: 人工快速审核
Step 4: 重新训练

预期时间: 2-3 天
预期提升: +5~10%
"""
import os
import sys
import numpy as np
from pathlib import Path

sys.path.insert(0, str(Path(__file__).parent.parent))


def step1_generate_pseudo_labels():
    """用最佳模型预测未标注数据,生成伪标签"""
    from ultralytics import YOLO

    # 加载最佳模型(明早从 overnight 实验中选)
    best_model_path = "runs/detect/Detection_experiments/ablation_D_cbam_p2_wiou/weights/best.pt"
    model = YOLO(best_model_path)

    # 预测未标注图片
    unlabeled_dir = "Data/Detection_dataset/images/unlabeled"  # 你的 3000 张未标注图
    results = model.predict(
        source=unlabeled_dir,
        save_txt=True,
        save_conf=True,
        conf=0.3,  # 低阈值,保留更多候选
        iou=0.6,
        imgsz=1280,
        project="pseudo_labels",
        name="round1",
    )

    print(f"✓ 生成伪标签完成: pseudo_labels/round1/labels/")
    return "pseudo_labels/round1/labels/"


def step2_active_learning_selection(pseudo_label_dir, top_k=500):
    """主动学习:选出最有价值的 top_k 张图让人标注

    不确定性估计方法:
    1. 置信度方差 (同一目标多次预测的置信度波动)
    2. 框数量异常 (过多或过少)
    3. 小目标占比高
    """
    import glob
    from collections import defaultdict

    label_files = glob.glob(os.path.join(pseudo_label_dir, "*.txt"))

    uncertainty_scores = []

    for lbl_file in label_files:
        with open(lbl_file) as f:
            lines = [l.strip().split() for l in f if l.strip()]

        if len(lines) == 0:
            # 空图,可能是漏检
            uncertainty_scores.append((lbl_file, 10.0, "empty"))
            continue

        # 提取置信度和框大小
        confs = [float(l[5]) for l in lines if len(l) >= 6]
        areas = [float(l[3]) * float(l[4]) for l in lines]

        # 不确定性指标
        conf_std = np.std(confs) if len(confs) > 1 else 0
        low_conf_ratio = sum(1 for c in confs if c < 0.5) / len(confs)
        small_obj_ratio = sum(1 for a in areas if a < 0.002) / len(areas)
        num_boxes = len(lines)

        # 综合得分(越高越不确定)
        score = (
            conf_std * 2.0 +
            low_conf_ratio * 3.0 +
            small_obj_ratio * 1.5 +
            abs(num_boxes - 31) / 31 * 1.0  # 偏离平均数
        )

        uncertainty_scores.append((lbl_file, score, f"conf_std={conf_std:.2f}"))

    # 排序,选 top_k
    uncertainty_scores.sort(key=lambda x: x[1], reverse=True)
    selected = uncertainty_scores[:top_k]

    # 保存选中的文件列表
    output_file = "active_learning_selected.txt"
    with open(output_file, "w") as f:
        for lbl_file, score, reason in selected:
            img_file = lbl_file.replace("/labels/", "/images/").replace(".txt", ".jpg")
            f.write(f"{img_file}\t{score:.3f}\t{reason}\n")

    print(f"✓ 主动学习选择完成: {top_k} 张高价值样本")
    print(f"  保存到: {output_file}")
    print(f"  平均不确定性: {np.mean([s[1] for s in selected]):.3f}")

    return output_file


def step3_human_review_tool(selected_file):
    """快速人工审核工具

    显示图片 + 伪标签,人工确认/修改/删除
    """
    print("\n=== 人工审核工具 ===")
    print("使用 LabelImg 或 CVAT 快速审核:")
    print(f"  1. 打开 {selected_file}")
    print(f"  2. 逐张检查伪标签")
    print(f"  3. 修改错误的框")
    print(f"  4. 删除误检")
    print(f"  5. 补充漏检")
    print()
    print("预计时间: 500 张 × 30秒/张 = 4 小时")
    print()
    input("审核完成后按 Enter 继续...")


def step4_merge_and_retrain(reviewed_labels_dir):
    """合并原始标注 + 审核后的伪标签,重新训练"""
    import shutil
    from ultralytics import YOLO

    # 创建新的训练集
    new_train_dir = "Data/Detection_dataset_v2"
    os.makedirs(f"{new_train_dir}/images/train", exist_ok=True)
    os.makedirs(f"{new_train_dir}/labels/train", exist_ok=True)

    # 复制原始训练集
    print("复制原始训练集...")
    shutil.copytree(
        "Data/Detection_dataset/images/train",
        f"{new_train_dir}/images/train",
        dirs_exist_ok=True
    )
    shutil.copytree(
        "Data/Detection_dataset/labels/train",
        f"{new_train_dir}/labels/train",
        dirs_exist_ok=True
    )

    # 添加审核后的伪标签
    print("添加审核后的伪标签...")
    reviewed_imgs = os.listdir(reviewed_labels_dir.replace("/labels/", "/images/"))
    for img_file in reviewed_imgs:
        shutil.copy(
            f"{reviewed_labels_dir.replace('/labels/', '/images/')}/{img_file}",
            f"{new_train_dir}/images/train/{img_file}"
        )
        lbl_file = img_file.replace(".jpg", ".txt")
        shutil.copy(
            f"{reviewed_labels_dir}/{lbl_file}",
            f"{new_train_dir}/labels/train/{lbl_file}"
        )

    print(f"✓ 新训练集创建完成: {new_train_dir}")
    print(f"  原始: 1536 张")
    print(f"  新增: {len(reviewed_imgs)} 张")
    print(f"  总计: {1536 + len(reviewed_imgs)} 张")

    # 更新 dataset.yaml
    yaml_content = f"""
path: {os.path.abspath(new_train_dir)}
train: images/train
val: ../Detection_dataset/images/val

nc: 1
names: ['goat']
"""
    with open(f"{new_train_dir}/dataset.yaml", "w") as f:
        f.write(yaml_content)

    # 重新训练
    print("\n开始重新训练...")
    from Scripts.modules.cbam import register_cbam
    from Scripts.modules.wise_iou import patch_wise_iou

    register_cbam()
    patch_wise_iou()

    model = YOLO("Models/yolo11s_cbam_p2.yaml")
    model.load("yolo11s.pt")

    model.train(
        data=f"{new_train_dir}/dataset.yaml",
        epochs=200,
        imgsz=1280,
        batch=4,
        patience=40,
        project="Detection_experiments",
        name="semi_supervised_v1",
        device=0,
    )

    print("✓ 重新训练完成")


def main():
    print("="*70)
    print("【Phase 1: 半监督学习 + 主动学习】")
    print("="*70)
    print()

    # Step 1: 生成伪标签
    print("Step 1: 生成伪标签...")
    pseudo_label_dir = step1_generate_pseudo_labels()

    # Step 2: 主动学习选择
    print("\nStep 2: 主动学习选择高价值样本...")
    selected_file = step2_active_learning_selection(pseudo_label_dir, top_k=500)

    # Step 3: 人工审核
    print("\nStep 3: 人工审核...")
    step3_human_review_tool(selected_file)

    # Step 4: 合并 + 重新训练
    print("\nStep 4: 合并数据集并重新训练...")
    reviewed_labels_dir = input("输入审核后的标签目录: ")
    step4_merge_and_retrain(reviewed_labels_dir)

    print("\n" + "="*70)
    print("Phase 1 完成!")
    print("预期提升: +5~10%")
    print("="*70)


if __name__ == "__main__":
    main()