有没有大佬知道DMM版的あやかしランブル立绘解包,就只找到个小人动态,不知道怎么组合

矩形挺好匹配的,目前应该没什么问题。

test
import os
import numpy as np
import cv2

def read_image_with_chinese_path(image_path):
    """读取含中文路径的图像,返回OpenCV图像对象"""
    try:
        with open(image_path, 'rb') as f:
            img_bytes = f.read()
        
        img_array = np.frombuffer(img_bytes, np.uint8)
        img = cv2.imdecode(img_array, cv2.IMREAD_UNCHANGED)
        return img
    except Exception as e:
        print(f"  读取图像失败 {image_path}:{str(e)}")
        return None

def overlay_image(main_img, overlay_img, x, y):
    """图像叠加函数,含透明通道处理,返回(处理后图像, 叠加是否成功)"""
    if main_img is None or overlay_img is None:
        print("  叠加失败:主图或表情图为空")
        return main_img, False
        
    # 获取图像尺寸
    h, w = overlay_img.shape[:2]
    main_h, main_w = main_img.shape[:2]
    
    # 检查坐标是否在主图范围内
    if x < 0 or y < 0 or x + w > main_w or y + h > main_h:
        print(f"  叠加失败:坐标({x},{y})超出主图范围")
        return main_img, False
    
    # 确保主图有Alpha通道,没有则添加
    if main_img.shape[2] != 4:
        b, g, r = cv2.split(main_img)
        a = np.ones((main_h, main_w), dtype=np.uint8) * 255  # 不透明
        main_img = cv2.merge([b, g, r, a])
    
    # 提取表情图的通道
    if overlay_img.shape[2] == 4:
        b_overlay, g_overlay, r_overlay, a_overlay = cv2.split(overlay_img)
        alpha = a_overlay / 255.0  # 归一化Alpha通道(0-255 -> 0-1)
        alpha_inv = 1.0 - alpha    # 反转alpha用于主图区域
    else:
        b_overlay, g_overlay, r_overlay = cv2.split(overlay_img)
        alpha = np.ones((h, w), dtype=np.float32)
        alpha_inv = np.zeros((h, w), dtype=np.float32)
    
    # 获取主图ROI区域
    main_roi = main_img[y:y+h, x:x+w]
    b_main, g_main, r_main, a_main = cv2.split(main_roi)
    
    # 执行叠加计算(NumPy向量化操作,比循环快)
    b_result = (alpha * b_overlay + alpha_inv * b_main).astype(np.uint8)
    g_result = (alpha * g_overlay + alpha_inv * g_main).astype(np.uint8)
    r_result = (alpha * r_overlay + alpha_inv * r_main).astype(np.uint8)
    a_result = (alpha * 255 + alpha_inv * a_main).astype(np.uint8)  # 叠加区域不透明
    
    # 合并通道
    roi_result = cv2.merge([b_result, g_result, r_result, a_result])
    
    # 将结果放回主图
    main_img[y:y+h, x:x+w] = roi_result
    
    return main_img, True

def get_best_overlay_position(main_img, facial_w, facial_h):
    """计算主图的最佳叠加位置(仅计算一次)"""
    main_h, main_w = main_img.shape[:2]
    main_a = main_img[:, :, 3]  # 提取主图Alpha通道
    main_center_x, main_center_y = main_w // 2, main_h // 2  # 主图中心(用于选最近区域)
    
    # 1. 生成透明区域二值掩码(Alpha=0→1,Alpha>0→0)
    mask = cv2.inRange(main_a, 0, 0) // 255  # 归一化到0/1
    
    # 2. 筛选能容纳表情图的透明连通块
    contours, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    min_area = facial_w * facial_h  # 表情图最小容纳面积
    valid_contours = []
    for cnt in contours:
        if cv2.contourArea(cnt) >= min_area:
            x, y, cw, ch = cv2.boundingRect(cnt)
            if cw >= facial_w and ch >= facial_h:  # 宽高也需满足
                valid_contours.append((x, y, cw, ch))
    
    if not valid_contours:
        print(f"  没有找到能容纳表情图({facial_w}x{facial_h})的透明连通块")
        return None
    
    # 3. 积分图加速候选区域搜索(O(1)判断区域透明度)
    candidates = []
    for (cx, cy, cw, ch) in valid_contours:
        # 连通块内可容纳表情图的坐标范围
        max_x = cx + cw - facial_w
        max_y = cy + ch - facial_h
        if max_x < cx or max_y < cy:
            continue
        
        # 生成连通块局部积分图
        contour_mask = mask[cy:cy+ch, cx:cx+cw]
        integral = cv2.integral(contour_mask)
        
        # 遍历所有可能位置,判断是否完全透明
        for y in range(cy, max_y + 1):
            for x in range(cx, max_x + 1):
                rel_x = x - cx
                rel_y = y - cy
                # 积分图计算窗口和:完全透明→窗口和=表情图像素总数
                window_sum = integral[rel_y + facial_h, rel_x + facial_w] - \
                            integral[rel_y, rel_x + facial_w] - \
                            integral[rel_y + facial_h, rel_x] + \
                            integral[rel_y, rel_x]
                
                if window_sum == facial_w * facial_h:
                    # 计算到主图中心的距离(选最近的位置)
                    region_center_x = x + facial_w // 2
                    region_center_y = y + facial_h // 2
                    distance = np.sqrt((region_center_x - main_center_x)**2 + 
                                     (region_center_y - main_center_y)** 2)
                    candidates.append((distance, x, y))
    
    if not candidates:
        print(f"  未找到适合表情图的透明区域")
        return None
    
    # 选择距离主图中心最近的位置
    candidates.sort()
    best_dist, best_x, best_y = candidates[0]
    print(f"  最佳叠加位置:({best_x}, {best_y})(到主图中心距离:{best_dist:.1f})")
    return best_x, best_y

def process_leaf_folder(input_leaf_path, output_leaf_path):
    """处理单个最后一级子文件夹的图像,返回(处理表情图数, 成功数, 失败清单)"""
    folder_total = 0
    folder_success = 0
    folder_fail_list = []
    
    # 1. 收集当前子文件夹下的PNG文件
    png_files = [os.path.join(input_leaf_path, f) for f in os.listdir(input_leaf_path) 
                 if f.lower().endswith('.png')]
    if not png_files:
        print(f"  子文件夹无PNG文件,跳过处理")
        return (folder_total, folder_success, folder_fail_list)
    
    # 2. 分类主图(不含"facial")和表情图(含"facial")
    facial_paths = [p for p in png_files if "facial" in os.path.basename(p)]
    main_paths = [p for p in png_files if "facial" not in os.path.basename(p)]
    
    if not main_paths:
        print(f"  未找到主图(文件名不含'facial'的PNG),跳过处理")
        return (folder_total, folder_success, folder_fail_list)
    if not facial_paths:
        print(f"  未找到表情图(文件名含'facial'的PNG),跳过处理")
        return (folder_total, folder_success, folder_fail_list)
    
    # 3. 处理每个主图(复用叠加位置)
    for main_idx, main_path in enumerate(main_paths, 1):
        main_img = read_image_with_chinese_path(main_path)
        if main_img is None:
            print(f"  跳过主图 {main_idx}/{len(main_paths)}:读取失败")
            continue
        
        main_h, main_w = main_img.shape[:2]
        main_name = os.path.splitext(os.path.basename(main_path))[0]
        print(f"\n  --- 处理主图 {main_idx}/{len(main_paths)}:{main_name}({main_w}x{main_h})---")
        
        # 保存原始主图(用于后续表情图叠加副本)
        original_main_img = main_img.copy()
        
        # 4. 检查主图Alpha通道(无则无法计算叠加区域)
        if main_img.shape[2] != 4:
            print(f"  主图无Alpha通道,无法计算叠加区域,跳过")
            continue
        
        # 5. 用第一个表情图尺寸计算最佳叠加位置(仅一次)
        first_facial = read_image_with_chinese_path(facial_paths[0])
        if first_facial is None:
            print(f"  参考表情图读取失败,无法计算叠加位置,跳过")
            continue
        facial_w, facial_h = first_facial.shape[1], first_facial.shape[0]  # 宽、高
        best_xy = get_best_overlay_position(main_img, facial_w, facial_h)
        if best_xy is None:
            print(f"  无法计算最佳叠加位置,跳过")
            continue
        best_x, best_y = best_xy
        
        # 6. 处理所有表情图(复用最佳位置)
        for facial_idx, facial_path in enumerate(facial_paths, 1):
            folder_total += 1  # 累计处理表情图总数
            facial_img = read_image_with_chinese_path(facial_path)
            
            # 表情图读取失败
            if facial_img is None:
                err_msg = f"表情图 {facial_idx}/{len(facial_paths)} 读取失败:{facial_path}"
                print(f"  {err_msg}")
                folder_fail_list.append(err_msg)
                continue
            
            facial_name = os.path.splitext(os.path.basename(facial_path))[0]
            f_w, f_h = facial_img.shape[1], facial_img.shape[0]
            print(f"\n  处理表情图 {facial_idx}/{len(facial_paths)}:{facial_name}({f_w}x{f_h})")
            
            # 表情图尺寸一致性警告
            if f_w != facial_w or f_h != facial_h:
                print(f"  警告:表情图尺寸与参考图不一致,叠加位置可能偏差")
            
            # 表情图尺寸超出主图
            if f_w > main_w or f_h > main_h:
                err_msg = f"表情图 {facial_idx}/{len(facial_paths)} 尺寸超出主图(表情图:{f_w}x{f_h},主图:{main_w}x{main_h}),跳过叠加:{facial_path}"
                print(f"  {err_msg}")
                folder_fail_list.append(err_msg)
                continue
            
            # 执行叠加
            temp_main = original_main_img.copy()
            temp_main, overlay_success = overlay_image(temp_main, facial_img, best_x, best_y)
            
            # 叠加失败
            if not overlay_success:
                err_msg = f"表情图 {facial_idx}/{len(facial_paths)} 叠加失败:{facial_path}"
                print(f"  {err_msg}")
                folder_fail_list.append(err_msg)
                continue
            
            # 叠加成功,保存结果
            output_filename = f"{facial_name}_{main_name}.png" if len(main_paths) > 1 else f"{facial_name}.png"
            output_save_path = os.path.join(output_leaf_path, output_filename)
            cv2.imencode('.png', temp_main)[1].tofile(output_save_path)
            
            print(f"  叠加完成,保存至:{output_save_path}")
            folder_success += 1  # 累计成功数
    
    print(f"\n  主图 {main_name} 处理完成")
    return (folder_total, folder_success, folder_fail_list)

if __name__ == "__main__":
    # 初始化全局统计变量
    total_facial = 0    # 总处理表情图数
    success_facial = 0  # 成功处理数
    fail_facial_list = []  # 失败清单
    
    # 1. 输入根文件夹路径
    input_root = input("请输入输入根文件夹地址:").strip()
    output_root = input("请输入输出根文件夹地址:").strip()
    
    # 2. 验证输入路径有效性
    if not os.path.isdir(input_root):
        print(f"错误:输入根文件夹 {input_root} 无效")
        exit()
    
    # 3. 创建输出根文件夹(若不存在)
    os.makedirs(output_root, exist_ok=True)
    
    # 4. 筛选所有最后一级子文件夹(叶子文件夹)
    leaf_folders = []
    for dirpath, dirs, _ in os.walk(input_root):
        if not dirs:  # dirs为空表示当前目录是最后一级
            leaf_folders.append(dirpath)
    
    if not leaf_folders:
        print(f"提示:输入根文件夹 {input_root} 下无最后一级子文件夹,程序将退出")
        exit()
    
    # 5. 处理每个叶子子文件夹并累计统计
    print(f"\n共找到 {len(leaf_folders)} 个最后一级子文件夹,开始处理...\n")
    for leaf_idx, input_leaf in enumerate(leaf_folders, 1):
        print(f"==== 处理第 {leaf_idx}/{len(leaf_folders)} 个子文件夹:{input_leaf} ====")
        
        # 构建输出子文件夹路径(保留原目录结构)
        rel_path = os.path.relpath(input_leaf, input_root)
        output_leaf = os.path.join(output_root, rel_path)
        os.makedirs(output_leaf, exist_ok=True)
        
        # 处理当前文件夹并获取统计数据
        folder_total, folder_success, folder_fail = process_leaf_folder(input_leaf, output_leaf)
        total_facial += folder_total
        success_facial += folder_success
        fail_facial_list.extend(folder_fail)
        
        print(f"子文件夹 {input_leaf} 处理完成,结果保存至 {output_leaf}\n")
    
    # 6. 计算失败数并输出最终统计
    fail_facial = total_facial - success_facial
    print("=" * 60)
    print("所有子文件夹处理完成!【最终统计】")
    print(f"处理表情图数:{total_facial}")
    print(f"成功数:{success_facial}")
    print(f"失败数:{fail_facial}")
    print("-" * 60)
    if fail_facial > 0:
        print("失败清单:")
        for idx, err in enumerate(fail_facial_list, 1):
            print(f"  {idx}. {err}")
    else:
        print("失败清单:无(所有表情图均处理成功)")
    print("=" * 60)
    input("任意键退出");
1 个赞

佬,能分享一下spine小人与涩涩cg吗?:joy:

可以去e里站看cg,以及通过torrent下载(怎么进自己查),有人持续更新(间隔时间为2-6月一次)。另外自己可以去加相关Q群,有人会分享cg。总不能日后次次都向大佬要吧?

:ok_hand:t2:,感谢大佬指路,在e站找到了cg :joy:

e站详细名字是什么,我也去翻翻宝

1 个赞
1 个赞

可能需要梯子 :joy: