diff --git a/.gitignore b/.gitignore index 6dd1646..83569d9 100644 --- a/.gitignore +++ b/.gitignore @@ -111,3 +111,9 @@ fill2sc/data/636394/636394.mtl fill2sc/data/636394/636394.zip fill2sc/data/636394/fill_636394_preview.png fill2sc/data/636394/index.txt +cartoon_texture/stain_mask_debug.png +cartoon_texture/data/642713/642713.jpg +cartoon_texture/data/642713/642713.mesh7.png +cartoon_texture/data/642713/642713.mtl +cartoon_texture/data/642713/642713.o.jpg +cartoon_texture/data/642713/642713.r.jpg diff --git a/cartoon_texture/fuse_textures.py b/cartoon_texture/fuse_textures.py new file mode 100644 index 0000000..5b02dfe --- /dev/null +++ b/cartoon_texture/fuse_textures.py @@ -0,0 +1,172 @@ +import cv2 +import numpy as np + +def detect_stain_mask(img2, img1, diff_thresh=0.12, grad_thresh=0.03, var_thresh=0.005): + """ + 检测图2中的印染区域(相对于图1的差异 + 高梯度 + 高局部方差) + + Args: + img2: 图2(清晰但有印染),uint8 RGB + img1: 图1(模糊但纯净),uint8 RGB + Returns: + mask: 浮点掩码 [0,1],1表示印染区域 + """ + # 转浮点 + f2 = img2.astype(np.float32) / 255.0 + f1 = img1.astype(np.float32) / 255.0 + + # 1. 差异图 + diff = np.abs(f2 - f1) + gray_diff = cv2.cvtColor(diff, cv2.COLOR_RGB2GRAY) + + # 2. 图2的拉普拉斯梯度(印染边缘高梯度) + gray2 = cv2.cvtColor(f2, cv2.COLOR_RGB2GRAY) + laplacian2 = cv2.Laplacian(gray2, cv2.CV_32F, ksize=3) + grad_mag = np.abs(laplacian2) + + # 3. 图2的局部方差(印染区域局部对比度高) + mean2 = cv2.GaussianBlur(f2, (0, 0), 5) + sqr_mean2 = cv2.GaussianBlur(f2 * f2, (0, 0), 5) + local_var = np.mean(sqr_mean2 - mean2 * mean2, axis=2) + + # 4. 组合条件 + mask_cond = ( + (gray_diff > diff_thresh) & + (grad_mag > grad_thresh) & + (local_var > var_thresh) + ).astype(np.float32) + + # 5. 形态学操作:膨胀连接区域 + 高斯模糊羽化 + kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5)) + mask = cv2.dilate(mask_cond, kernel, iterations=2) + mask = cv2.GaussianBlur(mask, (15, 15), 0) + + # 归一化到 [0, 1] + mask = np.clip(mask, 0, 1) + + return mask + + +def fuse_textures(img2, img1, mask, sharpen_img1=True): + """ + 融合两张纹理图 + + Args: + img2: 底图(清晰但有印染) + img1: 补丁图(模糊但纯净) + mask: 印染掩码 [0,1] + sharpen_img1: 是否对图1做轻微锐化以减少"糊"感 + Returns: + fused: 融合结果 + """ + f2 = img2.astype(np.float32) / 255.0 + f1 = img1.astype(np.float32) / 255.0 + + # 可选:对图1做轻微锐化 + if sharpen_img1: + # 非锐化掩模 + blurred = cv2.GaussianBlur(f1, (0, 0), 1.5) + sharpened = cv2.addWeighted(f1, 1.5, blurred, -0.5, 0) + f1_sharp = np.clip(sharpened, 0, 1) + else: + f1_sharp = f1 + + # 融合:mask区域用图1,其余保留图2 + mask_3ch = np.stack([mask, mask, mask], axis=2) + fused = f2 * (1.0 - mask_3ch) + f1_sharp * mask_3ch + + # 转回 uint8 + fused = np.clip(fused * 255.0, 0, 255).astype(np.uint8) + + return fused + + +def post_process_clahe(img, clip_limit=1.5, grid_size=(8, 8), mix_ratio=0.3): + """ + 轻微 CLAHE 后处理(仅 V 通道),与原图混合 + """ + hsv = cv2.cvtColor(img, cv2.COLOR_RGB2HSV).astype(np.float32) + h, s, v = cv2.split(hsv) + + # CLAHE 需要 uint8 + v_u8 = (v / 255.0 * 255).astype(np.uint8) + clahe = cv2.createCLAHE(clipLimit=clip_limit, tileGridSize=grid_size) + v_adjusted = clahe.apply(v_u8).astype(np.float32) + + # 混合 + v_mixed = v * (1 - mix_ratio) + v_adjusted * mix_ratio + v_mixed = np.clip(v_mixed, 0, 255) + + hsv_out = cv2.merge([h, s, v_mixed]).astype(np.uint8) + return cv2.cvtColor(hsv_out, cv2.COLOR_HSV2RGB) + + +def main(): + # === 参数配置 === + img1_path = "/home/algo/Documents/openMVS/openMVS/cartoon_texture/data/642713/642713.o.jpg" # 图1:模糊但纯净 + img2_path = "/home/algo/Documents/openMVS/openMVS/cartoon_texture/data/642713/642713.mesh7.png" # 图2:清晰但有印染 + output_path = "/home/algo/Documents/openMVS/openMVS/cartoon_texture/data/642713/642713.jpg" + + # 印染检测参数(可按实际效果微调) + DIFF_THRESH = 0.12 # 差异阈值 + GRAD_THRESH = 0.03 # 梯度阈值 + VAR_THRESH = 0.005 # 局部方差阈值 + + # 后处理参数 + APPLY_CLAHE = True # 是否做轻微CLAHE + SHARPEN_IMG1 = True # 是否锐化图1 + + # === 加载图片 === + img2 = cv2.imread(img2_path) + img1 = cv2.imread(img1_path) + + if img2 is None or img1 is None: + print("Error: Cannot load images") + return + + # 确保尺寸一致(以图2为准) + if img1.shape != img2.shape: + print(f"Resizing img1 from {img1.shape} to {img2.shape}") + img1 = cv2.resize(img1, (img2.shape[1], img2.shape[0])) + + # 转 RGB(OpenCV 默认 BGR) + img2_rgb = cv2.cvtColor(img2, cv2.COLOR_BGR2RGB) + img1_rgb = cv2.cvtColor(img1, cv2.COLOR_BGR2RGB) + + print("Detecting stain mask...") + mask = detect_stain_mask(img2_rgb, img1_rgb, + diff_thresh=DIFF_THRESH, + grad_thresh=GRAD_THRESH, + var_thresh=VAR_THRESH) + + print(f"Mask coverage: {np.mean(mask > 0.5) * 100:.1f}%") + + print("Fusing textures...") + fused = fuse_textures(img2_rgb, img1_rgb, mask, sharpen_img1=SHARPEN_IMG1) + + if APPLY_CLAHE: + print("Applying CLAHE post-processing...") + fused = post_process_clahe(fused) + + # 保存结果 + fused_bgr = cv2.cvtColor(fused, cv2.COLOR_RGB2BGR) + cv2.imwrite(output_path, fused_bgr) + + # 保存掩码可视化(调试用) + mask_viz = (mask * 255).astype(np.uint8) + cv2.imwrite("stain_mask_debug.png", mask_viz) + + print(f"Done! Result saved to {output_path}") + print(f"Debug mask saved to stain_mask_debug.png") + + # 显示结果 + cv2.imshow("Original (mesh7.1)", img2) + cv2.imshow("Reference (n1)", img1) + cv2.imshow("Stain Mask", mask_viz) + cv2.imshow("Fused Result", fused_bgr) + cv2.waitKey(0) + cv2.destroyAllWindows() + + +if __name__ == "__main__": + main() \ No newline at end of file