feat: 特殊车牌生成管线 + C++/多进程/色温校正

This commit is contained in:
kirl committed 2026-06-21 10:32:37 +08:00
1 parent 3808b9f035
commit 3101992f51
29 files changed
+2336 -80

No files matched your search

+295
View File
@@ -0,0 +1,295 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
车牌底板色手动调色 GUI
======================
用 HSV 滑块实时调整合成车牌底板色,预览效果,保存参数供管线调用。
用法:
python color_tuner.py
控件:
H 偏移 — 色相偏移量 (-30 ~ +30)
S 缩放%% — 饱和度缩放 (10% ~ 150%)
V 缩放%% — 明度缩放 (10% ~ 150%)
[1-9] — 切换车牌类型
[R] — 复位
[S] — 保存当前参数到 plate_color_profile.py
[ESC] — 退出
"""
import sys
from pathlib import Path
import cv2
import numpy as np
# ── 路径 ──
_SCRIPT_DIR = Path(__file__).resolve().parent
_PROJECT_DIR = _SCRIPT_DIR.parent.parent
_GENERATOR_DIR = _PROJECT_DIR / 'chinese_license_plate_generator'
sys.path.insert(0, str(_GENERATOR_DIR))
from generate_multi_plate import MultiPlateGenerator
from plate_number import (
generate_plate_number_white,
generate_plate_number_army,
generate_plate_number_black_gangao,
generate_plate_number_black_shi_old,
generate_plate_number_black_shi_new,
generate_plate_number_black_ling_old,
generate_plate_number_black_ling_new,
generate_plate_number_yellow_xue,
generate_plate_number_blue,
)
# ── 车牌类型 ──
PLATE_TYPES = [
('yellow', '教练车', generate_plate_number_yellow_xue),
('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),
('black_shi', '使馆旧', generate_plate_number_black_shi_old),
('black_shi', '使馆新', generate_plate_number_black_shi_new),
('black', '领馆旧', generate_plate_number_black_ling_old),
('black', '领馆新', generate_plate_number_black_ling_new),
]
# ── 参考图 ──
REF_PATH = Path(r"C:\Users\Kirl\Downloads\R-C (3).jpg")
# ── 全局状态 ──
generator = None
current_type_idx = 0
synthetic_plate = None
ref_image = None
# 各类型的默认参数 (H偏移, S缩放%, V缩放%)
params = {}
def init_generator():
global generator
pm = str(_GENERATOR_DIR / 'plate_model')
fm = str(_GENERATOR_DIR / 'font_model')
generator = MultiPlateGenerator(pm, fm)
def load_ref():
global ref_image
if REF_PATH.is_file():
ref = cv2.imread(str(REF_PATH))
if ref is not None:
h, w = ref.shape[:2]
ref = cv2.resize(ref, (int(w * 0.6), int(h * 0.6)))
ref_image = ref
def generate_plate(type_idx):
global synthetic_plate
bg_color, label, gen_func = PLATE_TYPES[type_idx]
number = gen_func()
plate_w = 480 if len(number) == 8 else 440
plate = generator.generate_plate_special(number, bg_color, is_double=False, enhance=False)
synthetic_plate = plate
return plate, number, label
def load_params(bg_color):
"""从已保存的 profile 加载参数"""
key = bg_color
if key not in params:
params[key] = {'h_shift': 0, 's_scale': 100, 'v_scale': 100}
return params[key]
def adjust_plate(plate_bgr, bg_color, h_shift, s_scale, v_scale):
"""
在 HSV 空间调整底板色。
仅作用于底板区域(高S + 对应色相范围 + 高V),字符不受影响。
"""
if h_shift == 0 and s_scale == 100 and v_scale == 100:
return plate_bgr.copy()
hue_ranges = {
'yellow': (15, 50), 'green_truck': (40, 85),
'white': (0, 180), 'white_army': (0, 180),
'black': (0, 180), 'black_shi': (0, 180),
}
h_lo, h_hi = hue_ranges.get(bg_color, (0, 180))
hsv = cv2.cvtColor(plate_bgr, cv2.COLOR_BGR2HSV).astype(np.float32)
h, s, v = hsv[:, :, 0], hsv[:, :, 1], hsv[:, :, 2]
# 底板区域 mask
hue_in = (h > h_lo) & (h < h_hi)
bg_mask = hue_in & (s > 80) & (v > 100)
if bg_mask.sum() < 200:
return plate_bgr.copy()
# 羽化
bg_u8 = bg_mask.astype(np.uint8) * 255
dist = cv2.distanceTransform(bg_u8, cv2.DIST_L2, cv2.DIST_MASK_PRECISE)
soft = np.clip(dist / 4.0, 0.0, 1.0)
h_new = h + h_shift * soft
s_new = s * (1.0 + (s_scale / 100.0 - 1.0) * soft)
v_new = v * (1.0 + (v_scale / 100.0 - 1.0) * soft)
hsv[:, :, 0] = np.clip(h_new, 0, 180)
hsv[:, :, 1] = np.clip(s_new, 0, 255)
hsv[:, :, 2] = np.clip(v_new, 0, 255)
return cv2.cvtColor(hsv.astype(np.uint8), cv2.COLOR_HSV2BGR)
def refresh():
global synthetic_plate
_, number, label = generate_plate(current_type_idx)
update_display()
def update_display(*_args):
bg_color, label, _ = PLATE_TYPES[current_type_idx]
p = load_params(bg_color)
# trackbar 可能在创建过程中尚未就绪,容错读取
try:
h_shift = cv2.getTrackbarPos('H offset', 'Color Tuner') - 30
s_scale = cv2.getTrackbarPos('S scale', 'Color Tuner')
v_scale = cv2.getTrackbarPos('V scale', 'Color Tuner')
except cv2.error:
h_shift = p.get('h_shift', 0)
s_scale = p.get('s_scale', 100)
v_scale = p.get('v_scale', 100)
p['h_shift'] = h_shift
p['s_scale'] = s_scale
p['v_scale'] = v_scale
adjusted = adjust_plate(synthetic_plate, bg_color, h_shift, s_scale, v_scale)
# 并排显示:原始 | 调整后
h, w = synthetic_plate.shape[:2]
display = np.hstack([synthetic_plate, adjusted])
# 加标签
cv2.putText(display, f"Original ({bg_color})", (10, 25),
cv2.FONT_HERSHEY_SIMPLEX, 0.55, (200, 200, 200), 1)
cv2.putText(display, f"Adjusted H:{h_shift:+d} S:{s_scale} V:{v_scale}",
(w + 10, 25), cv2.FONT_HERSHEY_SIMPLEX, 0.55, (0, 255, 200), 1)
# 类型标签
cv2.putText(display, f"[{current_type_idx+1}/9] {label}",
(10, display.shape[0] - 10),
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (180, 180, 180), 1)
# 如果参考图存在,放在下方
if ref_image is not None:
rw = display.shape[1]
rh = ref_image.shape[0] * rw // ref_image.shape[1]
ref_rs = cv2.resize(ref_image, (rw, rh))
display = np.vstack([display, np.ones((4, rw, 3), dtype=np.uint8) * 30, ref_rs])
cv2.putText(display, "Reference", (10, display.shape[0] - rh + 25),
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 255), 1)
cv2.imshow('Color Tuner', display)
def save_params():
"""保存当前所有类型的参数到 plate_color_profile.py"""
profile_path = _SCRIPT_DIR / 'plate_color_profile.py'
lines = ['# Auto-generated by color_tuner.py',
'# Plate background color correction profiles',
'# 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']:
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('}')
lines.append('')
profile_path.write_text('\n'.join(lines), encoding='utf-8')
print(f"\n[SAVED] {profile_path}")
print(f" Params: { {k: v for k, v in params.items()} }")
def on_key(key):
global current_type_idx, synthetic_plate
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('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}
# Reset trackbars
cv2.setTrackbarPos('H offset', 'Color Tuner', 30)
cv2.setTrackbarPos('S scale', 'Color Tuner', 100)
cv2.setTrackbarPos('V scale', 'Color Tuner', 100)
update_display()
elif key == ord('s') or key == ord('S'):
save_params()
elif key == ord('n') or key == ord('N'):
refresh()
def main():
global ref_image
init_generator()
load_ref()
# 初始化第一张车牌
_, number, label = generate_plate(current_type_idx)
# 先建窗口和滑动条,再刷新显示
cv2.namedWindow('Color Tuner', cv2.WINDOW_NORMAL)
cv2.resizeWindow('Color Tuner', 1000, 500)
bg_color = PLATE_TYPES[current_type_idx][0]
p = load_params(bg_color)
cv2.createTrackbar('H offset', 'Color Tuner', p['h_shift'] + 30, 60, update_display)
cv2.createTrackbar('S scale', 'Color Tuner', p['s_scale'], 150, update_display)
cv2.createTrackbar('V scale', 'Color Tuner', p['v_scale'], 150, update_display)
update_display()
print("=" * 60)
print(" 车牌底板色手动调色 GUI")
print("=" * 60)
print(" 按键:")
print(" 1-9 切换车牌类型")
print(" R 复位当前类型参数")
print(" N 生成新随机车牌")
print(" S 保存参数到 plate_color_profile.py")
print(" ESC 退出")
print("=" * 60)
print(f" 当前类型: {PLATE_TYPES[current_type_idx][1]}")
print("=" * 60)
while True:
key = cv2.waitKey(50) & 0xFF
if key == 27: # ESC
break
if key != 255:
on_key(key)
cv2.destroyAllWindows()
if __name__ == '__main__':
import os
os.chdir(str(_PROJECT_DIR))
main()
@@ -0,0 +1,145 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
CCPD 真实车牌 ROI 提取工具
==========================
从 CCPD 图片中透视校正并裁剪车牌区域,保存为统一尺寸的 ROI 图片。
用于 CycleGAN/CUT 风格对齐训练的真实车牌域数据。
用法:
python extract_real_plates.py --ccpd-dir ./CCPD/CCPD2019/ccpd_base --num 500 --output ./gan_data/real_plates
输出:
440×140 的校正车牌 ROI (BGR .jpg)
"""
import os
import sys
import argparse
import random
from pathlib import Path
from typing import Optional, Tuple
import cv2
import numpy as np
from tqdm import tqdm
def parse_ccpd_corners(filename: str) -> Optional[np.ndarray]:
"""
从 CCPD 文件名解析4角点,几何排序为 [TL, TR, BR, BL]。
"""
base = Path(filename).stem
parts = base.split('-')
if len(parts) < 5:
return None
try:
coords_str = parts[3]
pts = []
for pt in coords_str.split('_'):
x, y = pt.split('&')
pts.append((float(x), float(y)))
if len(pts) != 4:
return None
a = np.array(pts)
sums = a[:, 0] + a[:, 1]
diffs = a[:, 0] - a[:, 1]
tl = a[np.argmin(sums)]
tr = a[np.argmax(diffs)]
br = a[np.argmax(sums)]
bl = a[np.argmin(diffs)]
return np.float32([tl, tr, br, bl])
except (ValueError, IndexError):
return None
def extract_plate_roi(image_bgr: np.ndarray,
corners: np.ndarray,
output_size: Tuple[int, int] = (440, 140)) -> Optional[np.ndarray]:
"""
透视校正提取车牌 ROI。
"""
h, w = image_bgr.shape[:2]
# 校验角点在图像范围内
if corners.min() < -10 or corners[:, 0].max() > w + 10 or corners[:, 1].max() > h + 10:
return None
dst = np.float32([
[0, 0],
[output_size[0] - 1, 0],
[output_size[0] - 1, output_size[1] - 1],
[0, output_size[1] - 1],
])
M = cv2.getPerspectiveTransform(corners, dst)
roi = cv2.warpPerspective(image_bgr, M, output_size,
flags=cv2.INTER_CUBIC,
borderMode=cv2.BORDER_REPLICATE)
return roi
def main():
parser = argparse.ArgumentParser(description='CCPD 真实车牌 ROI 提取')
parser.add_argument('--ccpd-dir', type=str, required=True,
help='CCPD 图片目录(多个用逗号分隔)')
parser.add_argument('--num', type=int, default=500,
help='提取数量 (默认: 500)')
parser.add_argument('--output', type=str, default='./gan_data/real_plates',
help='输出目录')
parser.add_argument('--seed', type=int, default=42)
args = parser.parse_args()
random.seed(args.seed)
np.random.seed(args.seed)
# 收集图片
ccpd_dirs = [d.strip() for d in args.ccpd_dir.split(',') if d.strip()]
all_images = []
for d in ccpd_dirs:
dd = Path(d)
if not dd.is_dir():
print(f"[WARN] 目录不存在: {d}")
continue
for ext in ('*.jpg', '*.jpeg', '*.png'):
all_images.extend(dd.glob(ext))
random.shuffle(all_images)
print(f"CCPD 图片总数: {len(all_images)}")
print(f"目标提取数: {args.num}")
print(f"输出目录: {args.output}")
print("-" * 50)
os.makedirs(args.output, exist_ok=True)
success = 0
for img_path in tqdm(all_images, desc='提取真实车牌ROI', unit='张'):
if success >= args.num:
break
corners = parse_ccpd_corners(img_path.name)
if corners is None:
continue
img = cv2.imread(str(img_path))
if img is None:
continue
roi = extract_plate_roi(img, corners)
if roi is None:
continue
out_name = f"real_{img_path.stem}.jpg"
cv2.imwrite(os.path.join(args.output, out_name), roi,
[cv2.IMWRITE_JPEG_QUALITY, 95])
success += 1
print(f"\n完成! 提取: {success} 张")
if __name__ == '__main__':
main()
@@ -0,0 +1,45 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
生成纯合成车牌(无场景背景)作为 CUT Domain A
============================================
独立于 CCPD 原图,直接调用车牌生成器渲染纯车牌图像。
"""
import sys
import os
import random
from pathlib import Path
import cv2
import numpy as np
# 路径解析:定位 chinese_license_plate_generator
_SCRIPT_DIR = Path(__file__).resolve().parent # .../CCPD_SPE_GEN/cv_pipeline/
_PROJECT_DIR = _SCRIPT_DIR.parent.parent # .../ICE_Dataset/
_GENERATOR_DIR = _PROJECT_DIR / 'chinese_license_plate_generator'
if str(_GENERATOR_DIR) not in sys.path:
sys.path.insert(0, str(_GENERATOR_DIR))
from generate_multi_plate import MultiPlateGenerator
random.seed(42)
np.random.seed(42)
g = MultiPlateGenerator(
str(_GENERATOR_DIR / 'plate_model'),
str(_GENERATOR_DIR / 'font_model')
)
out = 'gan_data/domain_A_synthetic/police_pure'
os.makedirs(out, exist_ok=True)
for i in range(500):
num, bg, _ = g.generate_plate_number()
# 强制police类型
if '警' not in num:
num = num[:6] + '警'
img = g.generate_plate_special(num, 'white', False)
cv2.imwrite(f'{out}/synth_{i:04d}.jpg', img)
print(f'Done: {out} ({len(os.listdir(out))} images)')
@@ -0,0 +1,12 @@
# Auto-generated by color_tuner.py
# Plate background color correction profiles
# Format: (h_shift, s_scale_percent, v_scale_percent)
PLATE_COLOR_PROFILE = {
'yellow': (-2, 150, 100),
'green_truck': (0, 100, 100),
'white': (0, 100, 100),
'white_army': (0, 100, 100),
'black': (0, 100, 100),
'black_shi': (0, 100, 100),
}
File diff suppressed because it is too large. Load diff