File size: 4,999 Bytes
13d71c8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4580e7e
13d71c8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
# 文件名: processor.py

import io
import zipfile
import re
import cv2
import numpy as np
from ultralytics import YOLO
from pathlib import Path
import streamlit as st

# --- 缓存模型加载 ---
# 使用Streamlit的缓存功能,让模型只在第一次加载,极大提高后续运行速度
@st.cache_resource
def load_yolo_model():
    # 注意:这里的路径是相对于项目根目录的
    # 你需要把 best.pt 文件也上传到你的GitHub仓库
    model_path = "/app/src/best.pt"
    try:
        model = YOLO(model_path)
        return model
    except Exception as e:
        raise FileNotFoundError(f"无法加载YOLO模型!请确保 'best.pt' 文件在项目主目录中。错误: {e}")

def repair_image_in_memory(wm_data, orig_data, model, config):
    """在内存中对单对图片进行修复"""
    # 将字节数据解码为OpenCV图像
    wm_img_np = np.frombuffer(wm_data, np.uint8)
    orig_img_np = np.frombuffer(orig_data, np.uint8)
    high_res_img = cv2.imdecode(wm_img_np, cv2.IMREAD_COLOR)
    low_res_img = cv2.imdecode(orig_img_np, cv2.IMREAD_COLOR)
    
    if high_res_img is None or low_res_img is None: 
        return None, "图片解码失败"

    h_high, w_high, _ = high_res_img.shape
    
    search_x_start = int(w_high * config['SEARCH_REGION_RATIOS'][0])
    search_y_start = int(h_high * config['SEARCH_REGION_RATIOS'][1])
    search_x_end = int(w_high * config['SEARCH_REGION_RATIOS'][2])
    search_y_end = int(h_high * config['SEARCH_REGION_RATIOS'][3])
    
    search_region = high_res_img[search_y_start:search_y_end, search_x_start:search_x_end]
    
    results = model.predict(source=search_region, conf=config['YOLO_CONFIDENCE_THRESHOLD'], verbose=False)
    boxes = results[0].boxes
    if len(boxes) == 0:
        return None, "未在指定区域内定位到水印"

    all_xyxy = boxes.xyxy.cpu().numpy()
    x_min_rel = int(np.min(all_xyxy[:, 0]))
    y_min_rel = int(np.min(all_xyxy[:, 1]))
    x_max_rel = int(np.max(all_xyxy[:, 2]))
    y_max_rel = int(np.max(all_xyxy[:, 3]))

    x_min_abs = x_min_rel + search_x_start
    y_min_abs = y_min_rel + search_y_start
    x_max_abs = x_max_rel + search_x_start
    y_max_abs = y_max_rel + search_y_start
    
    original_width = x_max_abs - x_min_abs
    original_height = y_max_abs - y_min_abs
    
    width_margin = int((original_width * config['WIDTH_EXPANSION_RATIO']) / 2)
    height_margin = int((original_height * config['HEIGHT_EXPANSION_RATIO']) / 2)
    
    x_start = max(0, x_min_abs - width_margin)
    y_start = max(0, y_min_abs - height_margin)
    x_end = min(w_high, x_max_abs + width_margin)
    y_end = min(h_high, y_max_abs + height_margin)
    
    low_res_resized = cv2.resize(low_res_img, (w_high, h_high), interpolation=cv2.INTER_LANCZOS4)
    clean_patch = low_res_resized[y_start:y_end, x_start:x_end]

    if clean_patch.shape[0] == 0 or clean_patch.shape[1] == 0:
        return None, "修复补丁计算尺寸无效"
    
    high_res_img[y_start:y_end, x_start:x_end] = clean_patch
    
    # 将修复后的图像编码回字节流
    _, buffer = cv2.imencode('.jpg', high_res_img, [cv2.IMWRITE_JPEG_QUALITY, 98])
    return buffer.tobytes(), "修复成功"

def process_zip_in_memory(zip_file_obj, config, status_area):
    """在内存中处理上传的ZIP包"""
    model = load_yolo_model()
    report_lines = ["--- AI去水印处理报告 ---"]
    
    input_zip = zipfile.ZipFile(zip_file_obj, 'r')
    files_map = {Path(f).stem: f for f in input_zip.namelist()}
    
    wm_pattern = re.compile(r"(.+)-wm$")
    tasks = []
    
    for base_name_stem, full_path in files_map.items():
        m = wm_pattern.match(base_name_stem)
        if m:
            base_id = m.group(1)
            orig_full_path = files_map.get(f"{base_id}-orig")
            if orig_full_path:
                tasks.append((full_path, orig_full_path))
    
    if not tasks:
        raise ValueError("ZIP包中未找到任何有效的图片对 (如 'id-wm.jpg' 和 'id-orig.jpg')")

    output_zip_buffer = io.BytesIO()
    with zipfile.ZipFile(output_zip_buffer, 'a', zipfile.ZIP_DEFLATED, False) as output_zip:
        for i, (wm_path, orig_path) in enumerate(tasks):
            status_area.text(f"正在处理第 {i+1}/{len(tasks)} 对图片: {Path(wm_path).name}")
            
            wm_data = input_zip.read(wm_path)
            orig_data = input_zip.read(orig_path)
            
            repaired_data, message = repair_image_in_memory(wm_data, orig_data, model, config)
            
            if repaired_data:
                # 修复后的文件名保持和有水印图一致
                output_zip.writestr(Path(wm_path).name, repaired_data)
                report_lines.append(f"  [成功] {Path(wm_path).name} - {message}")
            else:
                report_lines.append(f"  [失败] {Path(wm_path).name} - {message}")

    report = "\n".join(report_lines)
    output_zip_buffer.seek(0)
    return output_zip_buffer, report