Dataset: add READMEs, organize scripts, update .gitignore

- Add README.md to all 16 dataset directories with script references
- Centralize generation scripts under scripts/ with full README
- Update .gitignore to cover all data directories, keep only metadata
- Remove redundant CCPD/.gitignore (parent covers same rules)
- Remove stale scripts/test/ (254MB pipeline residue)
- Fix label parser reference: scripts are authoritative source of truth
This commit is contained in:
kirl committed 2026-06-23 14:41:39 +08:00
1 parent 7867c3bfaf
commit a64690477a
43 files changed
+2691 -205

No files matched your search

+1 -1
View File
@@ -39,4 +39,4 @@ Write-Host "可执行文件: $buildDir\Release\plate_swap.exe"
# 使用示例
Write-Host ""
Write-Host "用法示例:"
Write-Host " .\build\Release\plate_swap.exe --ccpd-dir ./CCPD/CCPD2019/ccpd_base --plate-type police --plate-pool ./output/plate_pool --num 500 --workers 32 --fast"
Write-Host " .\build\Release\plate_swap.exe --ccpd-dir ./CCPD/CCPD2019/ccpd_base --plate-type police --plate-pool ./output/plate_pool --num 500 --workers 32"
@@ -49,6 +49,8 @@ from plate_number import (
# 类型配置
PLATE_TYPES = [
('blue', 'blue', generate_plate_number_blue),
('green', 'green_car', lambda: generate_plate_number_blue(length=8)),
('police', 'white', generate_plate_number_white),
('army', 'white_army', generate_plate_number_army),
('gangao', 'black', generate_plate_number_black_gangao),
-5
View File
@@ -18,7 +18,6 @@ struct Config {
string plate_pool_dir;
int num_images = 500;
unsigned workers = thread::hardware_concurrency();
bool fast = false;
int seed = 42;
int h_shift = 0, s_scale = 100, v_scale = 100;
};
@@ -50,7 +49,6 @@ Config parse_args(int argc, char* argv[]) {
else if (arg == "--num" && i+1 < argc) cfg.num_images = stoi(argv[++i]);
else if (arg == "--workers" && i+1 < argc) cfg.workers = stoi(argv[++i]);
else if (arg == "--seed" && i+1 < argc) cfg.seed = stoi(argv[++i]);
else if (arg == "--fast") cfg.fast = true;
else if (arg == "--h-shift" && i+1 < argc) cfg.h_shift = stoi(argv[++i]);
else if (arg == "--s-scale" && i+1 < argc) cfg.s_scale = stoi(argv[++i]);
else if (arg == "--v-scale" && i+1 < argc) cfg.v_scale = stoi(argv[++i]);
@@ -103,7 +101,6 @@ int main(int argc, char* argv[]) {
cerr << " --plate-pool <dir> Plate pool directory (required)" << endl;
cerr << " --num <N> Number of images (default 500)" << endl;
cerr << " --workers <N> Thread count (default all cores)" << endl;
cerr << " --fast Fast mode (skip degradation)" << endl;
return 1;
}
@@ -111,7 +108,6 @@ int main(int argc, char* argv[]) {
cout << "Type: " << cfg.plate_type << endl;
cout << "Threads: " << cfg.workers << endl;
cout << "Target: " << cfg.num_images << " images" << endl;
cout << "Fast: " << (cfg.fast ? "yes" : "no") << endl;
if (cfg.plate_pool_dir.empty()) {
cerr << "[ERROR] --plate-pool is required" << endl;
@@ -153,7 +149,6 @@ int main(int argc, char* argv[]) {
SwapConfig swap_cfg;
swap_cfg.plate_type = cfg.plate_type;
swap_cfg.output_dir = cfg.output_dir;
swap_cfg.fast_mode = cfg.fast;
swap_cfg.h_shift = cfg.h_shift;
swap_cfg.s_scale = cfg.s_scale;
swap_cfg.v_scale = cfg.v_scale;
+9 -28
View File
@@ -184,6 +184,8 @@ static string bg_color_of(const string& plate_type) {
if (plate_type == "lingguan_old" || plate_type == "lingguan_new") return "black";
if (plate_type == "coach") return "yellow";
if (plate_type == "new_energy") return "green_truck";
if (plate_type == "blue") return "blue";
if (plate_type == "green") return "green_car";
return "white";
}
@@ -322,7 +324,7 @@ Mat match_plate_color(const Mat& synthetic, const Mat& orig_roi,
// 白/绿牌
Scalar color_cast(white_ref[0]/ref_mean, white_ref[1]/ref_mean, white_ref[2]/ref_mean);
float strength = (bg_color == "green_truck") ? 1.5f : 1.0f;
float strength = (bg_color == "green_truck" || bg_color == "green_car") ? 1.5f : 1.0f;
Scalar amplified(1.0f + (color_cast[0]-1.0f)*strength, 1.0f+(color_cast[1]-1.0f)*strength, 1.0f+(color_cast[2]-1.0f)*strength);
amplified[0] = std::clamp((float)amplified[0], 0.85f, 1.15f);
amplified[1] = std::clamp((float)amplified[1], 0.85f, 1.15f);
@@ -359,7 +361,8 @@ Mat fix_bg_color(const Mat& plate, const string& bg_color,
int h_lo = 0, h_hi = 180;
if (bg_color == "yellow") { h_lo = 15; h_hi = 50; }
else if (bg_color == "green_truck") { h_lo = 40; h_hi = 85; }
else if (bg_color == "green_truck" || bg_color == "green_car") { h_lo = 40; h_hi = 85; }
else if (bg_color == "blue") { h_lo = 90; h_hi = 130; }
Mat hsv;
cvtColor(plate, hsv, COLOR_BGR2HSV);
@@ -629,7 +632,7 @@ bool process_single(const string& img_path, const SwapConfig& cfg,
if (current_w > 10 && abs(target_w - current_w) > 10) {
float l_ratio = target_w / current_w;
float min_c = 0.5f, max_c = 1.1f;
if (bg_color == "green_truck" || bg_color == "yellow") { min_c = 0.4f; max_c = 1.0f; }
if (bg_color == "green_truck" || bg_color == "green_car" || bg_color == "yellow") { min_c = 0.4f; max_c = 1.0f; }
else if (bg_color == "black" || bg_color == "black_shi") { min_c = 0.3f; max_c = 1.2f; }
l_ratio = std::clamp(l_ratio, min_c, max_c);
@@ -650,38 +653,16 @@ bool process_single(const string& img_path, const SwapConfig& cfg,
}
// 8. Degradation
if (!cfg.fast_mode)
matched = degrade_plate(matched);
matched = degrade_plate(matched);
// 9-11. Warp + blend + noise
auto [warped, mask] = warp_plate_back(matched, meta.corners, Size(w_img, h_img));
Mat blended = feather_blend(img_orig, warped, mask);
Mat final = match_noise(blended, img_orig, mask);
// 12. Save with pinyin tags
// 12. Save with original Chinese plate number (imwrite_unicode supports UTF-8)
string stem = fp.stem().string();
// Convert Chinese plate markers to ASCII tags
string tag = plate_number;
// Manual pinyin mapping for common plate characters (UTF-8 byte sequences)
// 港=E6B8AF 澳=E6BEB3 警=E8ADA6 使=E4BDBF 领=E9A286 学=E5ADA6
struct { const char* utf8; const char* py; } map[] = {
{"\xE6\xB8\xAF", "GANG"}, {"\xE6\xBE\xB3", "AO"},
{"\xE8\xAD\xA6", "JING"}, {"\xE4\xBD\xBF", "SHI"},
{"\xE9\xA2\x86", "LING"}, {"\xE5\xAD\xA6", "XUE"},
};
for (auto& m : map) {
size_t pos = 0;
while ((pos = tag.find(m.utf8, pos)) != string::npos) {
tag.replace(pos, 3, m.py);
pos += strlen(m.py);
}
}
// Remove any remaining non-ASCII
string clean;
for (char c : tag) {
if ((unsigned char)c < 128) clean += c;
}
string out_name = stem + "__" + cfg.plate_type + "__" + clean + ".jpg";
string out_name = stem + "__" + cfg.plate_type + "__" + plate_number + ".jpg";
string ccpd_subdir = fp.parent_path().filename().string();
fs::path out_dir = fs::path(cfg.output_dir) / ccpd_subdir / cfg.plate_type;
fs::create_directories(out_dir);
-1
View File
@@ -34,7 +34,6 @@ struct SwapConfig {
std::string plate_type;
std::string output_dir;
int h_shift = 0, s_scale = 100, v_scale = 100;
bool fast_mode = false;
};
// 单张处理 (pre-parsed meta 避免重复解析)
+26 -15
View File
@@ -45,8 +45,10 @@ from plate_number import (
# ── 车牌类型 ──
PLATE_TYPES = [
('blue', '蓝牌', generate_plate_number_blue),
('green_car', '小新能源', lambda: generate_plate_number_blue(length=8)),
('yellow', '教练车', generate_plate_number_yellow_xue),
('green_truck','新能源', lambda: generate_plate_number_blue(length=8)),
('green_truck','大新能源', lambda: generate_plate_number_blue(length=8)),
('white', '警车', generate_plate_number_white),
('white_army', '军车', generate_plate_number_army),
('black', '港澳', generate_plate_number_black_gangao),
@@ -113,7 +115,8 @@ def adjust_plate(plate_bgr, bg_color, h_shift, s_scale, v_scale):
return plate_bgr.copy()
hue_ranges = {
'yellow': (15, 50), 'green_truck': (40, 85),
'yellow': (15, 50), 'green_truck': (40, 85), 'green_car': (40, 85),
'blue': (90, 130),
'white': (0, 180), 'white_army': (0, 180),
'black': (0, 180), 'black_shi': (0, 180),
}
@@ -182,7 +185,7 @@ def update_display(*_args):
(w + 10, 25), cv2.FONT_HERSHEY_SIMPLEX, 0.55, (0, 255, 200), 1)
# 类型标签
cv2.putText(display, f"[{current_type_idx+1}/9] {label}",
cv2.putText(display, f"[{current_type_idx+1}/{len(PLATE_TYPES)}] {label}",
(10, display.shape[0] - 10),
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (180, 180, 180), 1)
@@ -206,7 +209,8 @@ def save_params():
'# Format: (h_shift, s_scale_percent, v_scale_percent)',
'',
'PLATE_COLOR_PROFILE = {']
for bg_color in ['yellow', 'green_truck', 'white', 'white_army', 'black', 'black_shi']:
for bg_color in ['yellow', 'green_truck', 'green_car', 'blue',
'white', 'white_army', 'black', 'black_shi']:
p = params.get(bg_color, {'h_shift': 0, 's_scale': 100, 'v_scale': 100})
lines.append(f" '{bg_color}': ({p['h_shift']}, {p['s_scale']}, {p['v_scale']}),")
lines.append('}')
@@ -219,19 +223,26 @@ def save_params():
def on_key(key):
global current_type_idx, synthetic_plate
# keys: 1-9, 0(=10), -(=11) — 切换车牌类型
idx = -1
if ord('1') <= key <= ord('9'):
idx = key - ord('1')
if idx < len(PLATE_TYPES):
current_type_idx = idx
# 切换类型时复位 trackbar 到该类型已保存的参数
bg_color = PLATE_TYPES[current_type_idx][0]
p = load_params(bg_color)
cv2.setTrackbarPos('H offset', 'Color Tuner', p['h_shift'] + 30)
cv2.setTrackbarPos('S scale', 'Color Tuner', p['s_scale'])
cv2.setTrackbarPos('V scale', 'Color Tuner', p['v_scale'])
# 生成新车牌
synthetic_plate, _, _ = generate_plate(current_type_idx)
update_display()
elif key == ord('0'):
idx = 9
elif key == ord('-'):
idx = 10
if 0 <= idx < len(PLATE_TYPES):
current_type_idx = idx
# 切换类型时复位 trackbar 到该类型已保存的参数
bg_color = PLATE_TYPES[current_type_idx][0]
p = load_params(bg_color)
cv2.setTrackbarPos('H offset', 'Color Tuner', p['h_shift'] + 30)
cv2.setTrackbarPos('S scale', 'Color Tuner', p['s_scale'])
cv2.setTrackbarPos('V scale', 'Color Tuner', p['v_scale'])
# 生成新车牌
synthetic_plate, _, _ = generate_plate(current_type_idx)
update_display()
elif key == ord('r') or key == ord('R'):
bg_color = PLATE_TYPES[current_type_idx][0]
params[bg_color] = {'h_shift': 0, 's_scale': 100, 'v_scale': 100}
@@ -3,8 +3,10 @@
# Format: (h_shift, s_scale_percent, v_scale_percent)
PLATE_COLOR_PROFILE = {
'yellow': (-2, 150, 100),
'green_truck': (0, 100, 100),
'yellow': (0, 150, 150),
'green_truck': (0, 100, 150),
'green_car': (-4, 69, 92),
'blue': (-7, 150, 150),
'white': (0, 100, 100),
'white_army': (0, 100, 100),
'black': (0, 100, 100),
+30 -31
View File
@@ -156,6 +156,16 @@ PLATE_TYPE_CONFIG = {
'generator': lambda: generate_plate_number_blue(length=8),
'description': '大型新能源'
},
'blue': {
'bg_color': 'blue',
'generator': generate_plate_number_blue,
'description': '蓝牌小型汽车'
},
'green': {
'bg_color': 'green_car',
'generator': lambda: generate_plate_number_blue(length=8),
'description': '小型新能源'
},
}
@@ -407,7 +417,7 @@ def match_plate_color(synthetic_plate: np.ndarray,
color_cast = white_ref / ref_mean
strength_map = {
'white': 1.0, 'white_army': 1.0,
'green_truck': 1.5,
'blue': 1.0, 'green_car': 1.5, 'green_truck': 1.5,
}
strength = strength_map.get(bg_color, 2.0)
@@ -492,7 +502,8 @@ def fix_plate_bg_color(plate_bgr: np.ndarray, bg_color: str) -> np.ndarray:
# 色相范围
hue_ranges = {
'yellow': (15, 50), 'green_truck': (40, 85),
'yellow': (15, 50), 'green_truck': (40, 85), 'green_car': (40, 85),
'blue': (90, 130),
'white': (0, 180), 'white_army': (0, 180),
'black': (0, 180), 'black_shi': (0, 180),
}
@@ -741,11 +752,10 @@ def match_image_quality(result: np.ndarray,
_worker_pipeline = None
def _worker_init(plate_type, fast):
def _worker_init(plate_type):
"""进程初始化:每个 worker 启动时调用一次(加载字体、底板缓存)"""
global _worker_pipeline
_worker_pipeline = PlateSwapPipeline(plate_type=plate_type)
_worker_pipeline._fast_mode = fast
def _worker_process_single(args):
@@ -791,9 +801,6 @@ class PlateSwapPipeline:
font_dir = str(_GENERATOR_DIR / 'font_model')
self.generator = MultiPlateGenerator(plate_model_dir, font_dir)
# 快速模式标志(多进程时由 _worker_process_single 设置)
self._fast_mode = False
def generate_plate_number(self) -> str:
"""生成随机特殊车牌号码"""
return self.number_generator()
@@ -864,7 +871,8 @@ class PlateSwapPipeline:
# 不同底板色用不同下限,防止白底变灰、绿牌变亮
clip_cfg = {
'white': (0.5, 1.1), 'white_army': (0.5, 1.1),
'green_truck': (0.4, 1.0),
'blue': (0.5, 1.1),
'green_truck': (0.4, 1.0), 'green_car': (0.4, 1.0),
'yellow': (0.4, 1.0),
'black': (0.3, 1.2), 'black_shi': (0.3, 1.2),
}
@@ -879,16 +887,15 @@ class PlateSwapPipeline:
matched_plate = (matched_plate.astype(np.float32) * effective_ratio)
matched_plate = np.clip(matched_plate, 0, 255).astype(np.uint8)
# ---- 8. 退化模拟(快速模式下跳过) ----
if not self._fast_mode:
# JPEG 极限压缩
matched_plate = jpeg_compress(matched_plate, quality=2)
# 降采样→升采样 模拟马赛克/像素化
h_p, w_p = matched_plate.shape[:2]
small = cv2.resize(matched_plate, (w_p // 4, h_p // 4), interpolation=cv2.INTER_LINEAR)
matched_plate = cv2.resize(small, (w_p, h_p), interpolation=cv2.INTER_NEAREST)
# 较强模糊
matched_plate = cv2.GaussianBlur(matched_plate, (5, 5), 1.5)
# ---- 8. 退化模拟 ----
# JPEG 极限压缩
matched_plate = jpeg_compress(matched_plate, quality=2)
# 降采样→升采样 模拟马赛克/像素化
h_p, w_p = matched_plate.shape[:2]
small = cv2.resize(matched_plate, (w_p // 4, h_p // 4), interpolation=cv2.INTER_LINEAR)
matched_plate = cv2.resize(small, (w_p, h_p), interpolation=cv2.INTER_NEAREST)
# 较强模糊
matched_plate = cv2.GaussianBlur(matched_plate, (5, 5), 1.5)
# ---- 9. 透视变换回贴 ----
warped, plate_mask = warp_plate_back(
@@ -903,7 +910,7 @@ class PlateSwapPipeline:
# ---- 12. 保存 ----
basename = Path(filename).stem
out_name = f"{basename}_{self.plate_type}_{new_plate_number}.jpg"
out_name = f"{basename}__{self.plate_type}__{new_plate_number}.jpg"
out_path = os.path.join(output_dir, out_name)
_imwrite_unicode(out_path, final, [cv2.IMWRITE_JPEG_QUALITY, 95])
@@ -927,8 +934,7 @@ class PlateSwapPipeline:
num_images: int = 500,
preview: bool = False,
seed: int = 42,
workers: int = 1,
fast: bool = False):
workers: int = 1):
"""
批量处理 CCPD 图片目录。
@@ -939,7 +945,6 @@ class PlateSwapPipeline:
preview: 是否保存调试中间结果
seed: 随机种子
workers: 并行进程数 (1=单进程, >1=多进程)
fast: 快速模式 (跳过退化模拟步骤)
"""
random.seed(seed)
np.random.seed(seed)
@@ -969,8 +974,6 @@ class PlateSwapPipeline:
print(f"目标生成数: {num_images}")
print(f"输出目录: {output_dir}")
print(f"并行进程: {workers}" + (" (多核加速)" if workers > 1 else ""))
if fast:
print(f"快速模式: 跳过退化模拟")
print("-" * 60)
import time
@@ -984,7 +987,7 @@ class PlateSwapPipeline:
else:
# 多进程模式
success, failed = self._process_parallel(
image_paths, output_dir, num_images, preview, workers, fast
image_paths, output_dir, num_images, preview, workers
)
elapsed = time.perf_counter() - t0
@@ -1013,7 +1016,7 @@ class PlateSwapPipeline:
return success, failed
def _process_parallel(self, image_paths, output_dir, num_images,
preview, workers, fast):
preview, workers):
"""多进程并行处理(worker 复用 Pipeline,仅初始化一次)"""
from multiprocessing import Pool, cpu_count
n_procs = min(workers, cpu_count())
@@ -1022,7 +1025,7 @@ class PlateSwapPipeline:
# 每个 worker 启动时加载一次 Pipeline(含字体、底板缓存)
with Pool(n_procs, initializer=_worker_init,
initargs=(self.plate_type, fast)) as pool:
initargs=(self.plate_type,)) as pool:
# 参数精简: (img_path, output_dir, preview_bool)
args = [
(p, output_dir, (preview and i < 10))
@@ -1074,9 +1077,6 @@ def main():
help='随机种子 (默认: 42)')
parser.add_argument('--workers', type=int, default=1,
help='并行进程数 (默认: 1, 建议设为CPU核心数)')
parser.add_argument('--fast', action='store_true',
help='快速模式:跳过退化模拟,速度提升约40%%')
args = parser.parse_args()
ccpd_dirs = [d.strip() for d in args.ccpd_dir.split(',') if d.strip()]
@@ -1088,7 +1088,6 @@ def main():
preview=args.preview,
seed=args.seed,
workers=args.workers,
fast=args.fast,
)