20250701
This commit is contained in:
8
.idea/.gitignore
generated
vendored
Normal file
8
.idea/.gitignore
generated
vendored
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
# 默认忽略的文件
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
||||||
|
# 基于编辑器的 HTTP 客户端请求
|
||||||
|
/httpRequests/
|
||||||
|
# Datasource local storage ignored files
|
||||||
|
/dataSources/
|
||||||
|
/dataSources.local.xml
|
||||||
14
.idea/deployment.xml
generated
Normal file
14
.idea/deployment.xml
generated
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="PublishConfigData" remoteFilesAllowedToDisappearOnAutoupload="false">
|
||||||
|
<serverData>
|
||||||
|
<paths name="root@192.168.1.102:22 password">
|
||||||
|
<serverdata>
|
||||||
|
<mappings>
|
||||||
|
<mapping local="$PROJECT_DIR$" web="/" />
|
||||||
|
</mappings>
|
||||||
|
</serverdata>
|
||||||
|
</paths>
|
||||||
|
</serverData>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
6
.idea/inspectionProfiles/profiles_settings.xml
generated
Normal file
6
.idea/inspectionProfiles/profiles_settings.xml
generated
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<component name="InspectionProjectProfileManager">
|
||||||
|
<settings>
|
||||||
|
<option name="USE_PROJECT_PROFILE" value="false" />
|
||||||
|
<version value="1.0" />
|
||||||
|
</settings>
|
||||||
|
</component>
|
||||||
7
.idea/misc.xml
generated
Normal file
7
.idea/misc.xml
generated
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="Black">
|
||||||
|
<option name="sdkName" value="Python 3.12 (pythonProject) (5)" />
|
||||||
|
</component>
|
||||||
|
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.12 (pythonProject) (5)" project-jdk-type="Python SDK" />
|
||||||
|
</project>
|
||||||
8
.idea/modules.xml
generated
Normal file
8
.idea/modules.xml
generated
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectModuleManager">
|
||||||
|
<modules>
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/.idea/pythonProject.iml" filepath="$PROJECT_DIR$/.idea/pythonProject.iml" />
|
||||||
|
</modules>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
10
.idea/pythonProject.iml
generated
Normal file
10
.idea/pythonProject.iml
generated
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module type="PYTHON_MODULE" version="4">
|
||||||
|
<component name="NewModuleRootManager">
|
||||||
|
<content url="file://$MODULE_DIR$">
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/.venv" />
|
||||||
|
</content>
|
||||||
|
<orderEntry type="inheritedJdk" />
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
</component>
|
||||||
|
</module>
|
||||||
6
.idea/vcs.xml
generated
Normal file
6
.idea/vcs.xml
generated
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
121
analyzer.py
Normal file
121
analyzer.py
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
import cv2
|
||||||
|
import numpy as np
|
||||||
|
import json
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
|
||||||
|
class WatermarkAnalyzer:
|
||||||
|
def __init__(self, config_path="config/default_config.json"):
|
||||||
|
self.watermark_regions = []
|
||||||
|
self.selected_regions = []
|
||||||
|
self.config_path = config_path
|
||||||
|
self.load_config()
|
||||||
|
self._check_opencv_version()
|
||||||
|
|
||||||
|
def _check_opencv_version(self):
|
||||||
|
"""检查OpenCV版本,确定是否支持显著性检测"""
|
||||||
|
self.has_saliency = hasattr(cv2, 'saliency')
|
||||||
|
if not self.has_saliency:
|
||||||
|
print("警告: 当前OpenCV版本不支持显著性检测,将使用替代方法")
|
||||||
|
|
||||||
|
def load_config(self):
|
||||||
|
try:
|
||||||
|
with open(self.config_path, 'r') as f:
|
||||||
|
config = json.load(f)
|
||||||
|
self.inpaint_radius = config.get("inpaint_radius", 5)
|
||||||
|
self.inpaint_algorithm = config.get("inpaint_algorithm", cv2.INPAINT_TELEA)
|
||||||
|
self.dilate_size = config.get("dilate_size", 3)
|
||||||
|
self.use_adaptive_mask = config.get("use_adaptive_mask", True)
|
||||||
|
self.text_threshold = config.get("text_threshold", 180)
|
||||||
|
self.min_text_area = config.get("min_text_area", 100)
|
||||||
|
self.text_color_range = tuple(
|
||||||
|
tuple(c) for c in config.get("text_color_range", ((50, 50, 50), (180, 180, 180))))
|
||||||
|
self.stamp_color_range = tuple(
|
||||||
|
tuple(c) for c in config.get("stamp_color_range", ((0, 0, 100), (100, 100, 255))))
|
||||||
|
self.saliency_threshold = config.get("saliency_threshold", 0.5)
|
||||||
|
self.edge_threshold = config.get("edge_threshold", 100)
|
||||||
|
self.use_mser = config.get("use_mser", True)
|
||||||
|
self.use_color_filter = config.get("use_color_filter", True)
|
||||||
|
self.use_texture_analysis = config.get("use_texture_analysis", True)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"加载配置失败: {e}")
|
||||||
|
self.reset_to_defaults()
|
||||||
|
|
||||||
|
# ... 其他方法保持不变 ...
|
||||||
|
|
||||||
|
def _detect_by_saliency(self, img):
|
||||||
|
"""基于显著性检测水印 - 兼容旧版OpenCV"""
|
||||||
|
if self.has_saliency:
|
||||||
|
# 使用标准显著性检测(如果支持)
|
||||||
|
try:
|
||||||
|
saliency = cv2.saliency.StaticSaliencySpectralResidual_create()
|
||||||
|
_, saliency_map = saliency.computeSaliency(img)
|
||||||
|
|
||||||
|
# 阈值处理
|
||||||
|
_, saliency_mask = cv2.threshold(saliency_map * 255, self.saliency_threshold * 255, 255,
|
||||||
|
cv2.THRESH_BINARY)
|
||||||
|
saliency_mask = saliency_mask.astype(np.uint8)
|
||||||
|
|
||||||
|
# 形态学操作
|
||||||
|
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))
|
||||||
|
closed = cv2.morphologyEx(saliency_mask, cv2.MORPH_CLOSE, kernel)
|
||||||
|
|
||||||
|
# 查找轮廓
|
||||||
|
contours, _ = cv2.findContours(closed, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
|
||||||
|
|
||||||
|
regions = []
|
||||||
|
for contour in contours:
|
||||||
|
x, y, w, h = cv2.boundingRect(contour)
|
||||||
|
area = w * h
|
||||||
|
if area > self.min_text_area * 2 and area < img.shape[0] * img.shape[1] * 0.1:
|
||||||
|
regions.append({
|
||||||
|
'type': 'saliency',
|
||||||
|
'position': (x, y, w, h),
|
||||||
|
'confidence': 0.7,
|
||||||
|
'contour': contour
|
||||||
|
})
|
||||||
|
|
||||||
|
return regions
|
||||||
|
except Exception as e:
|
||||||
|
print(f"显著性检测失败: {e}")
|
||||||
|
self.has_saliency = False # 标记为不支持,下次使用替代方法
|
||||||
|
|
||||||
|
# 替代方法:使用亮度变化检测
|
||||||
|
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
|
||||||
|
|
||||||
|
# 高斯模糊减少噪声
|
||||||
|
blurred = cv2.GaussianBlur(gray, (15, 15), 0)
|
||||||
|
|
||||||
|
# 计算拉普拉斯算子检测边缘
|
||||||
|
laplacian = cv2.Laplacian(blurred, cv2.CV2.CV_64F)
|
||||||
|
laplacian = np.uint8(np.absolute(laplacian))
|
||||||
|
|
||||||
|
# 阈值处理
|
||||||
|
_, threshold = cv2.threshold(laplacian, 30, 255, cv2.THRESH_BINARY)
|
||||||
|
|
||||||
|
# 形态学操作
|
||||||
|
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))
|
||||||
|
closed = cv2.morphologyEx(threshold, cv2.MORPH_CLOSE, kernel)
|
||||||
|
|
||||||
|
# 查找轮廓
|
||||||
|
contours, _ = cv2.findContours(closed, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
|
||||||
|
|
||||||
|
regions = []
|
||||||
|
for contour in contours:
|
||||||
|
x, y, w, h = cv2.boundingRect(contour)
|
||||||
|
area = w * h
|
||||||
|
if area > self.min_text_area * 2 and area < img.shape[0] * img.shape[1] * 0.1:
|
||||||
|
# 计算轮廓的紧致度
|
||||||
|
perimeter = cv2.arcLength(contour, True)
|
||||||
|
compactness = 4 * np.pi * area / (perimeter * perimeter) if perimeter > 0 else 0
|
||||||
|
|
||||||
|
# 水印通常具有中等紧致度
|
||||||
|
if 0.1 < compactness < 0.9:
|
||||||
|
regions.append({
|
||||||
|
'type': 'contrast',
|
||||||
|
'position': (x, y, w, h),
|
||||||
|
'confidence': 0.6,
|
||||||
|
'contour': contour
|
||||||
|
})
|
||||||
|
|
||||||
|
return regions
|
||||||
21
config/defaut_config.json
Normal file
21
config/defaut_config.json
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"inpaint_radius": 5,
|
||||||
|
"inpaint_algorithm": 0,
|
||||||
|
"dilate_size": 3,
|
||||||
|
"use_adaptive_mask": true,
|
||||||
|
"text_threshold": 180,
|
||||||
|
"min_text_area": 100,
|
||||||
|
"text_color_range": [
|
||||||
|
[50, 50, 50],
|
||||||
|
[180, 180, 180]
|
||||||
|
],
|
||||||
|
"stamp_color_range": [
|
||||||
|
[0, 0, 100],
|
||||||
|
[100, 100, 255]
|
||||||
|
],
|
||||||
|
"saliency_threshold": 0.5,
|
||||||
|
"edge_threshold": 100,
|
||||||
|
"use_mser": true,
|
||||||
|
"use_color_filter": true,
|
||||||
|
"use_texture_analysis": true
|
||||||
|
}
|
||||||
766
gui.py
Normal file
766
gui.py
Normal file
@ -0,0 +1,766 @@
|
|||||||
|
import os
|
||||||
|
import cv2
|
||||||
|
import numpy as np
|
||||||
|
from PIL import Image, ImageTk
|
||||||
|
import tkinter as tk
|
||||||
|
from tkinter import filedialog, messagebox, ttk
|
||||||
|
import threading
|
||||||
|
import time
|
||||||
|
|
||||||
|
|
||||||
|
class WatermarkGUI:
|
||||||
|
def __init__(self, root, analyzer):
|
||||||
|
self.root = root
|
||||||
|
self.analyzer = analyzer
|
||||||
|
self.source_folder = ""
|
||||||
|
self.output_folder = ""
|
||||||
|
self.current_image = None
|
||||||
|
self.current_preview = None
|
||||||
|
self.image_files = []
|
||||||
|
self.current_index = 0
|
||||||
|
self.processing = False
|
||||||
|
self.analyzing = False
|
||||||
|
|
||||||
|
# 设置中文字体
|
||||||
|
self.font_family = "SimHei" # Windows系统默认中文字体
|
||||||
|
if os.name == "posix": # macOS/Linux
|
||||||
|
self.font_family = "WenQuanYi Micro Hei"
|
||||||
|
|
||||||
|
self.create_widgets()
|
||||||
|
|
||||||
|
def create_widgets(self):
|
||||||
|
"""创建GUI组件"""
|
||||||
|
# 主框架
|
||||||
|
main_frame = ttk.Frame(self.root, padding="20")
|
||||||
|
main_frame.pack(fill=tk.BOTH, expand=True)
|
||||||
|
|
||||||
|
# 顶部信息栏
|
||||||
|
info_frame = ttk.Frame(main_frame)
|
||||||
|
info_frame.pack(fill=tk.X, pady=(0, 10))
|
||||||
|
|
||||||
|
ttk.Label(info_frame, text="图片水印分析与去除工具 V9",
|
||||||
|
font=(self.font_family, 16, "bold")).pack(side=tk.LEFT)
|
||||||
|
|
||||||
|
# 文件夹选择区域
|
||||||
|
folder_frame = ttk.LabelFrame(main_frame, text="文件夹设置", padding="10")
|
||||||
|
folder_frame.pack(fill=tk.X, pady=10)
|
||||||
|
|
||||||
|
# 源文件夹
|
||||||
|
ttk.Label(folder_frame, text="源文件夹:", font=(self.font_family, 10)).grid(row=0, column=0, sticky=tk.W,
|
||||||
|
pady=5)
|
||||||
|
self.source_entry = ttk.Entry(folder_frame, width=60)
|
||||||
|
self.source_entry.grid(row=0, column=1, padx=5, pady=5)
|
||||||
|
ttk.Button(folder_frame, text="浏览...", command=self.browse_source_folder,
|
||||||
|
style='Accent.TButton').grid(row=0, column=2, padx=5, pady=5)
|
||||||
|
|
||||||
|
# 输出文件夹
|
||||||
|
ttk.Label(folder_frame, text="输出文件夹:", font=(self.font_family, 10)).grid(row=1, column=0, sticky=tk.W,
|
||||||
|
pady=5)
|
||||||
|
self.output_entry = ttk.Entry(folder_frame, width=60)
|
||||||
|
self.output_entry.grid(row=1, column=1, padx=5, pady=5)
|
||||||
|
ttk.Button(folder_frame, text="浏览...", command=self.browse_output_folder,
|
||||||
|
style='Accent.TButton').grid(row=1, column=2, padx=5, pady=5)
|
||||||
|
|
||||||
|
# 分析和处理区域
|
||||||
|
process_frame = ttk.Frame(main_frame)
|
||||||
|
process_frame.pack(fill=tk.X, pady=10)
|
||||||
|
|
||||||
|
# 左侧分析区域
|
||||||
|
analysis_frame = ttk.LabelFrame(process_frame, text="水印分析", padding="10")
|
||||||
|
analysis_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=(0, 5))
|
||||||
|
|
||||||
|
self.analyze_button = ttk.Button(analysis_frame, text="分析当前图片", command=self.analyze_watermark,
|
||||||
|
style='Accent.TButton')
|
||||||
|
self.analyze_button.pack(side=tk.LEFT, padx=5, pady=5)
|
||||||
|
|
||||||
|
self.analysis_status = ttk.Label(analysis_frame, text="未分析水印",
|
||||||
|
font=(self.font_family, 10))
|
||||||
|
self.analysis_status.pack(side=tk.LEFT, padx=10, pady=5)
|
||||||
|
|
||||||
|
# 右侧处理区域
|
||||||
|
process_button_frame = ttk.LabelFrame(process_frame, text="处理", padding="10")
|
||||||
|
process_button_frame.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True, padx=(5, 0))
|
||||||
|
|
||||||
|
ttk.Button(process_button_frame, text="去除选中水印", command=self.process_current_image,
|
||||||
|
style='Accent.TButton').pack(side=tk.LEFT, padx=5, pady=5)
|
||||||
|
|
||||||
|
ttk.Button(process_button_frame, text="批量处理所有图片", command=self.process_all_images,
|
||||||
|
style='Accent.TButton').pack(side=tk.RIGHT, padx=5, pady=5)
|
||||||
|
|
||||||
|
# 水印选择区域
|
||||||
|
selection_frame = ttk.LabelFrame(main_frame, text="选择要去除的水印", padding="10")
|
||||||
|
selection_frame.pack(fill=tk.X, pady=10)
|
||||||
|
|
||||||
|
# 创建水印列表
|
||||||
|
columns = ("type", "x", "y", "width", "height", "confidence", "select")
|
||||||
|
self.watermark_tree = ttk.Treeview(selection_frame, columns=columns, show="headings", height=4)
|
||||||
|
|
||||||
|
# 设置列标题
|
||||||
|
self.watermark_tree.heading("type", text="类型")
|
||||||
|
self.watermark_tree.heading("x", text="X坐标")
|
||||||
|
self.watermark_tree.heading("y", text="Y坐标")
|
||||||
|
self.watermark_tree.heading("width", text="宽度")
|
||||||
|
self.watermark_tree.heading("height", text="高度")
|
||||||
|
self.watermark_tree.heading("confidence", text="置信度")
|
||||||
|
self.watermark_tree.heading("select", text="选择")
|
||||||
|
|
||||||
|
# 设置列宽
|
||||||
|
self.watermark_tree.column("type", width=80, anchor=tk.CENTER)
|
||||||
|
self.watermark_tree.column("x", width=60, anchor=tk.CENTER)
|
||||||
|
self.watermark_tree.column("y", width=60, anchor=tk.CENTER)
|
||||||
|
self.watermark_tree.column("width", width=60, anchor=tk.CENTER)
|
||||||
|
self.watermark_tree.column("height", width=60, anchor=tk.CENTER)
|
||||||
|
self.watermark_tree.column("confidence", width=80, anchor=tk.CENTER)
|
||||||
|
self.watermark_tree.column("select", width=60, anchor=tk.CENTER)
|
||||||
|
|
||||||
|
self.watermark_tree.pack(fill=tk.X, pady=5)
|
||||||
|
|
||||||
|
# 选择按钮
|
||||||
|
button_frame = ttk.Frame(selection_frame)
|
||||||
|
button_frame.pack(fill=tk.X, pady=5)
|
||||||
|
|
||||||
|
ttk.Button(button_frame, text="选择所有", command=self.select_all_watermarks).pack(side=tk.LEFT, padx=5)
|
||||||
|
ttk.Button(button_frame, text="取消选择", command=self.deselect_all_watermarks).pack(side=tk.LEFT, padx=5)
|
||||||
|
ttk.Button(button_frame, text="应用选择", command=self.apply_watermark_selection).pack(side=tk.RIGHT, padx=5)
|
||||||
|
|
||||||
|
# 检测参数设置
|
||||||
|
params_frame = ttk.LabelFrame(main_frame, text="检测参数设置", padding="10")
|
||||||
|
params_frame.pack(fill=tk.X, pady=10)
|
||||||
|
|
||||||
|
# 第一行参数
|
||||||
|
row1_frame = ttk.Frame(params_frame)
|
||||||
|
row1_frame.pack(fill=tk.X, pady=5)
|
||||||
|
|
||||||
|
# 文字敏感度
|
||||||
|
ttk.Label(row1_frame, text="文字敏感度:").grid(row=0, column=0, sticky=tk.W, pady=5, padx=5)
|
||||||
|
self.text_sensitivity = tk.IntVar(value=30)
|
||||||
|
text_scale = ttk.Scale(row1_frame, from_=10, to=100, orient=tk.HORIZONTAL,
|
||||||
|
variable=self.text_sensitivity, length=200, command=self.on_text_sensitivity_change)
|
||||||
|
text_scale.grid(row=0, column=1, padx=5, pady=5)
|
||||||
|
self.text_sensitivity_value = ttk.Label(row1_frame, text=str(self.text_sensitivity.get()))
|
||||||
|
self.text_sensitivity_value.grid(row=0, column=2, padx=5, pady=5)
|
||||||
|
|
||||||
|
# 印章敏感度
|
||||||
|
ttk.Label(row1_frame, text="印章敏感度:").grid(row=0, column=3, sticky=tk.W, pady=5, padx=5)
|
||||||
|
self.stamp_sensitivity = tk.IntVar(value=30)
|
||||||
|
stamp_scale = ttk.Scale(row1_frame, from_=10, to=100, orient=tk.HORIZONTAL,
|
||||||
|
variable=self.stamp_sensitivity, length=200, command=self.on_stamp_sensitivity_change)
|
||||||
|
stamp_scale.grid(row=0, column=4, padx=5, pady=5)
|
||||||
|
self.stamp_sensitivity_value = ttk.Label(row1_frame, text=str(self.stamp_sensitivity.get()))
|
||||||
|
self.stamp_sensitivity_value.grid(row=0, column=5, padx=5, pady=5)
|
||||||
|
|
||||||
|
# 第二行参数
|
||||||
|
row2_frame = ttk.Frame(params_frame)
|
||||||
|
row2_frame.pack(fill=tk.X, pady=5)
|
||||||
|
|
||||||
|
# 显著性阈值
|
||||||
|
ttk.Label(row2_frame, text="显著性阈值:").grid(row=0, column=0, sticky=tk.W, pady=5, padx=5)
|
||||||
|
self.saliency_threshold = tk.DoubleVar(value=self.analyzer.saliency_threshold)
|
||||||
|
saliency_scale = ttk.Scale(row2_frame, from_=0.1, to=0.9, orient=tk.HORIZONTAL,
|
||||||
|
variable=self.saliency_threshold, length=200,
|
||||||
|
command=lambda s: self.saliency_threshold.set(round(float(s), 1)))
|
||||||
|
saliency_scale.grid(row=0, column=1, padx=5, pady=5)
|
||||||
|
self.saliency_value = ttk.Label(row2_frame, text=str(self.saliency_threshold.get()))
|
||||||
|
self.saliency_value.grid(row=0, column=2, padx=5, pady=5)
|
||||||
|
|
||||||
|
# 边缘阈值
|
||||||
|
ttk.Label(row2_frame, text="边缘阈值:").grid(row=0, column=3, sticky=tk.W, pady=5, padx=5)
|
||||||
|
self.edge_threshold = tk.IntVar(value=self.analyzer.edge_threshold)
|
||||||
|
edge_scale = ttk.Scale(row2_frame, from_=50, to=200, orient=tk.HORIZONTAL,
|
||||||
|
variable=self.edge_threshold, length=200, command=self.on_edge_threshold_change)
|
||||||
|
edge_scale.grid(row=0, column=4, padx=5, pady=5)
|
||||||
|
self.edge_value = ttk.Label(row2_frame, text=str(self.edge_threshold.get()))
|
||||||
|
self.edge_value.grid(row=0, column=5, padx=5, pady=5)
|
||||||
|
|
||||||
|
# 第三行参数 - 检测方法选择
|
||||||
|
row3_frame = ttk.Frame(params_frame)
|
||||||
|
row3_frame.pack(fill=tk.X, pady=5)
|
||||||
|
|
||||||
|
self.use_color_filter = tk.BooleanVar(value=self.analyzer.use_color_filter)
|
||||||
|
ttk.Checkbutton(row3_frame, text="颜色过滤", variable=self.use_color_filter,
|
||||||
|
command=self.on_detection_method_change).grid(row=0, column=0, sticky=tk.W, pady=5, padx=5)
|
||||||
|
|
||||||
|
self.use_mser = tk.BooleanVar(value=self.analyzer.use_mser)
|
||||||
|
ttk.Checkbutton(row3_frame, text="文字检测(MSER)", variable=self.use_mser,
|
||||||
|
command=self.on_detection_method_change).grid(row=0, column=1, sticky=tk.W, pady=5, padx=5)
|
||||||
|
|
||||||
|
self.use_texture_analysis = tk.BooleanVar(value=self.analyzer.use_texture_analysis)
|
||||||
|
ttk.Checkbutton(row3_frame, text="纹理分析", variable=self.use_texture_analysis,
|
||||||
|
command=self.on_detection_method_change).grid(row=0, column=2, sticky=tk.W, pady=5, padx=5)
|
||||||
|
|
||||||
|
# 修复参数设置
|
||||||
|
repair_frame = ttk.LabelFrame(main_frame, text="修复参数设置", padding="10")
|
||||||
|
repair_frame.pack(fill=tk.X, pady=10)
|
||||||
|
|
||||||
|
# 第一行修复参数
|
||||||
|
repair_row1_frame = ttk.Frame(repair_frame)
|
||||||
|
repair_row1_frame.pack(fill=tk.X, pady=5)
|
||||||
|
|
||||||
|
# 修复半径
|
||||||
|
ttk.Label(repair_row1_frame, text="修复半径:").grid(row=0, column=0, sticky=tk.W, pady=5, padx=5)
|
||||||
|
self.repair_radius = tk.IntVar(value=self.analyzer.inpaint_radius)
|
||||||
|
repair_scale = ttk.Scale(repair_row1_frame, from_=1, to=15, orient=tk.HORIZONTAL,
|
||||||
|
variable=self.repair_radius, length=200, command=self.on_repair_radius_change)
|
||||||
|
repair_scale.grid(row=0, column=1, padx=5, pady=5)
|
||||||
|
self.repair_radius_value = ttk.Label(repair_row1_frame, text=str(self.repair_radius.get()))
|
||||||
|
self.repair_radius_value.grid(row=0, column=2, padx=5, pady=5)
|
||||||
|
|
||||||
|
# 修复算法
|
||||||
|
ttk.Label(repair_row1_frame, text="修复算法:").grid(row=0, column=3, sticky=tk.W, pady=5, padx=5)
|
||||||
|
self.repair_algorithm = tk.StringVar(value="TELEA")
|
||||||
|
algorithm_combo = ttk.Combobox(repair_row1_frame, textvariable=self.repair_algorithm,
|
||||||
|
values=["TELEA", "NS"], state="readonly", width=10)
|
||||||
|
algorithm_combo.grid(row=0, column=4, padx=5, pady=5)
|
||||||
|
algorithm_combo.bind("<<ComboboxSelected>>", self.on_repair_algorithm_change)
|
||||||
|
|
||||||
|
# 图像预览区域
|
||||||
|
preview_frame = ttk.LabelFrame(main_frame, text="图像预览", padding="10")
|
||||||
|
preview_frame.pack(fill=tk.BOTH, expand=True, pady=10)
|
||||||
|
|
||||||
|
# 创建画布
|
||||||
|
self.canvas_frame = ttk.Frame(preview_frame)
|
||||||
|
self.canvas_frame.pack(fill=tk.BOTH, expand=True)
|
||||||
|
|
||||||
|
self.canvas = tk.Canvas(self.canvas_frame, bg="white")
|
||||||
|
self.canvas.pack(fill=tk.BOTH, expand=True)
|
||||||
|
|
||||||
|
# 添加滚动条
|
||||||
|
self.h_scrollbar = ttk.Scrollbar(self.canvas_frame, orient=tk.HORIZONTAL, command=self.canvas.xview)
|
||||||
|
self.v_scrollbar = ttk.Scrollbar(self.canvas_frame, orient=tk.VERTICAL, command=self.canvas.yview)
|
||||||
|
|
||||||
|
self.canvas.configure(xscrollcommand=self.h_scrollbar.set, yscrollcommand=self.v_scrollbar.set)
|
||||||
|
|
||||||
|
self.h_scrollbar.pack(side=tk.BOTTOM, fill=tk.X)
|
||||||
|
self.v_scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
|
||||||
|
|
||||||
|
# 图像导航
|
||||||
|
nav_frame = ttk.Frame(main_frame)
|
||||||
|
nav_frame.pack(fill=tk.X, pady=10)
|
||||||
|
|
||||||
|
ttk.Button(nav_frame, text="上一张", command=self.prev_image).pack(side=tk.LEFT, padx=5)
|
||||||
|
self.image_info = ttk.Label(nav_frame, text="未加载图片", font=(self.font_family, 10))
|
||||||
|
self.image_info.pack(side=tk.LEFT, padx=20)
|
||||||
|
ttk.Button(nav_frame, text="下一张", command=self.next_image).pack(side=tk.RIGHT, padx=5)
|
||||||
|
|
||||||
|
# 状态栏
|
||||||
|
self.status_bar = ttk.Label(self.root, text="就绪", relief=tk.SUNKEN, anchor=tk.W)
|
||||||
|
self.status_bar.pack(side=tk.BOTTOM, fill=tk.X)
|
||||||
|
|
||||||
|
# 绑定鼠标滚轮事件
|
||||||
|
self.canvas.bind("<MouseWheel>", self.on_mousewheel) # Windows
|
||||||
|
self.canvas.bind("<Button-4>", self.on_mousewheel) # Linux
|
||||||
|
self.canvas.bind("<Button-5>", self.on_mousewheel) # Linux
|
||||||
|
|
||||||
|
# 绑定画布大小变化事件
|
||||||
|
self.canvas.bind("<Configure>", self.on_canvas_configure)
|
||||||
|
|
||||||
|
def browse_source_folder(self):
|
||||||
|
"""浏览并选择源文件夹"""
|
||||||
|
folder = filedialog.askdirectory(title="选择源文件夹")
|
||||||
|
if folder:
|
||||||
|
self.source_folder = folder
|
||||||
|
self.source_entry.delete(0, tk.END)
|
||||||
|
self.source_entry.insert(0, folder)
|
||||||
|
self.load_images()
|
||||||
|
|
||||||
|
def browse_output_folder(self):
|
||||||
|
"""浏览并选择输出文件夹"""
|
||||||
|
folder = filedialog.askdirectory(title="选择输出文件夹")
|
||||||
|
if folder:
|
||||||
|
self.output_folder = folder
|
||||||
|
self.output_entry.delete(0, tk.END)
|
||||||
|
self.output_entry.insert(0, folder)
|
||||||
|
|
||||||
|
def load_images(self):
|
||||||
|
"""加载文件夹中的所有图像"""
|
||||||
|
if not self.source_folder:
|
||||||
|
return
|
||||||
|
|
||||||
|
self.image_files = []
|
||||||
|
valid_extensions = ['.jpg', '.jpeg', '.png', '.bmp', '.tiff', '.gif']
|
||||||
|
|
||||||
|
for file in os.listdir(self.source_folder):
|
||||||
|
if any(file.lower().endswith(ext) for ext in valid_extensions):
|
||||||
|
self.image_files.append(os.path.join(self.source_folder, file))
|
||||||
|
|
||||||
|
if self.image_files:
|
||||||
|
self.current_index = 0
|
||||||
|
self.load_current_image()
|
||||||
|
self.update_image_info()
|
||||||
|
else:
|
||||||
|
messagebox.showinfo("提示", "所选文件夹中没有找到图像文件")
|
||||||
|
|
||||||
|
def load_current_image(self):
|
||||||
|
"""加载当前选中的图像"""
|
||||||
|
if not self.image_files:
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.current_image = cv2.imread(self.image_files[self.current_index])
|
||||||
|
if self.current_image is None:
|
||||||
|
messagebox.showerror("错误", f"无法加载图像: {self.image_files[self.current_index]}")
|
||||||
|
return
|
||||||
|
|
||||||
|
# 重置水印分析结果
|
||||||
|
self.analyzer.watermark_regions = []
|
||||||
|
self.update_watermark_tree()
|
||||||
|
self.analysis_status.config(text="未分析水印")
|
||||||
|
|
||||||
|
# 显示图像
|
||||||
|
self.display_image()
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
messagebox.showerror("错误", f"加载图像时出错: {str(e)}")
|
||||||
|
|
||||||
|
def display_image(self):
|
||||||
|
"""在画布上显示当前图像"""
|
||||||
|
if self.current_image is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
# 复制图像用于显示
|
||||||
|
display_img = self.current_image.copy()
|
||||||
|
|
||||||
|
# 如果有选中的水印区域,在预览图上标记
|
||||||
|
for region in self.analyzer.selected_regions:
|
||||||
|
x, y, w, h = region['position']
|
||||||
|
color = (0, 255, 0) # 绿色边框
|
||||||
|
cv2.rectangle(display_img, (x, y), (x + w, y + h), color, 2)
|
||||||
|
|
||||||
|
# 转换为PIL格式
|
||||||
|
rgb_img = cv2.cvtColor(display_img, cv2.COLOR_BGR2RGB)
|
||||||
|
pil_img = Image.fromarray(rgb_img)
|
||||||
|
|
||||||
|
# 调整图像大小以适应画布
|
||||||
|
canvas_width = self.canvas.winfo_width() - 20
|
||||||
|
canvas_height = self.canvas.winfo_height() - 20
|
||||||
|
|
||||||
|
if canvas_width <= 0 or canvas_height <= 0:
|
||||||
|
return
|
||||||
|
|
||||||
|
img_width, img_height = pil_img.size
|
||||||
|
|
||||||
|
# 计算缩放比例
|
||||||
|
scale = min(canvas_width / img_width, canvas_height / img_height)
|
||||||
|
|
||||||
|
if scale < 1:
|
||||||
|
pil_img = pil_img.resize((int(img_width * scale), int(img_height * scale)), Image.LANCZOS)
|
||||||
|
|
||||||
|
# 转换为Tkinter可用的格式
|
||||||
|
self.current_preview = ImageTk.PhotoImage(image=pil_img)
|
||||||
|
|
||||||
|
# 在画布上显示图像
|
||||||
|
self.canvas.delete("all")
|
||||||
|
self.canvas.create_image(10, 10, anchor=tk.NW, image=self.current_preview)
|
||||||
|
|
||||||
|
# 更新画布滚动区域
|
||||||
|
self.canvas.configure(scrollregion=self.canvas.bbox("all"))
|
||||||
|
|
||||||
|
def update_image_info(self):
|
||||||
|
"""更新图像信息标签"""
|
||||||
|
if not self.image_files:
|
||||||
|
self.image_info.config(text="未加载图片")
|
||||||
|
return
|
||||||
|
|
||||||
|
self.image_info.config(
|
||||||
|
text=f"图片 {self.current_index + 1}/{len(self.image_files)}: {os.path.basename(self.image_files[self.current_index])}")
|
||||||
|
|
||||||
|
def prev_image(self):
|
||||||
|
"""显示上一张图片"""
|
||||||
|
if not self.image_files:
|
||||||
|
return
|
||||||
|
|
||||||
|
self.current_index = (self.current_index - 1) % len(self.image_files)
|
||||||
|
self.load_current_image()
|
||||||
|
self.update_image_info()
|
||||||
|
|
||||||
|
def next_image(self):
|
||||||
|
"""显示下一张图片"""
|
||||||
|
if not self.image_files:
|
||||||
|
return
|
||||||
|
|
||||||
|
self.current_index = (self.current_index + 1) % len(self.image_files)
|
||||||
|
self.load_current_image()
|
||||||
|
self.update_image_info()
|
||||||
|
|
||||||
|
def analyze_watermark(self):
|
||||||
|
"""分析当前图片中的水印"""
|
||||||
|
if self.current_image is None:
|
||||||
|
messagebox.showinfo("提示", "请先加载图片")
|
||||||
|
return
|
||||||
|
|
||||||
|
if self.analyzing:
|
||||||
|
messagebox.showinfo("提示", "正在进行水印分析,请稍候...")
|
||||||
|
return
|
||||||
|
|
||||||
|
# 禁用分析按钮
|
||||||
|
self.analyze_button.config(state=tk.DISABLED)
|
||||||
|
self.analysis_status.config(text="分析中...")
|
||||||
|
self.analyzing = True
|
||||||
|
|
||||||
|
# 更新分析参数
|
||||||
|
self.update_analysis_parameters()
|
||||||
|
|
||||||
|
# 在单独的线程中执行水印分析
|
||||||
|
analysis_thread = threading.Thread(target=self._analyze_watermark_thread)
|
||||||
|
analysis_thread.daemon = True
|
||||||
|
analysis_thread.start()
|
||||||
|
|
||||||
|
def _analyze_watermark_thread(self):
|
||||||
|
"""在后台线程中执行水印分析"""
|
||||||
|
try:
|
||||||
|
# 执行水印分析
|
||||||
|
start_time = time.time()
|
||||||
|
self.analyzer.analyze(self.current_image)
|
||||||
|
analysis_time = time.time() - start_time
|
||||||
|
|
||||||
|
# 更新UI
|
||||||
|
self.root.after(0, self._update_after_analysis, analysis_time)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self.root.after(0, messagebox.showerror, "错误", f"分析水印时出错: {str(e)}")
|
||||||
|
self.root.after(0, self._reset_analysis_state)
|
||||||
|
|
||||||
|
def _update_after_analysis(self, analysis_time):
|
||||||
|
"""分析完成后更新UI"""
|
||||||
|
# 更新水印列表
|
||||||
|
self.update_watermark_tree()
|
||||||
|
|
||||||
|
# 更新状态
|
||||||
|
if self.analyzer.watermark_regions:
|
||||||
|
self.analysis_status.config(
|
||||||
|
text=f"已检测到 {len(self.analyzer.watermark_regions)} 个水印区域 (耗时: {analysis_time:.2f}秒)")
|
||||||
|
else:
|
||||||
|
self.analysis_status.config(text="未检测到水印 (耗时: {analysis_time:.2f}秒)")
|
||||||
|
|
||||||
|
# 重新显示图像(包含水印标记)
|
||||||
|
self.display_image()
|
||||||
|
|
||||||
|
# 重置分析状态
|
||||||
|
self._reset_analysis_state()
|
||||||
|
|
||||||
|
def _reset_analysis_state(self):
|
||||||
|
"""重置分析状态"""
|
||||||
|
self.analyze_button.config(state=tk.NORMAL)
|
||||||
|
self.analyzing = False
|
||||||
|
|
||||||
|
def update_watermark_tree(self):
|
||||||
|
"""更新水印树状视图"""
|
||||||
|
# 清空现有项
|
||||||
|
for item in self.watermark_tree.get_children():
|
||||||
|
self.watermark_tree.delete(item)
|
||||||
|
|
||||||
|
# 添加新项
|
||||||
|
for i, region in enumerate(self.analyzer.watermark_regions):
|
||||||
|
x, y, w, h = region['position']
|
||||||
|
confidence = region['confidence']
|
||||||
|
r_type = region['type']
|
||||||
|
|
||||||
|
# 判断是否选中
|
||||||
|
is_selected = region in self.analyzer.selected_regions
|
||||||
|
select_text = "✔" if is_selected else ""
|
||||||
|
|
||||||
|
self.watermark_tree.insert("", tk.END, values=(
|
||||||
|
r_type, x, y, w, h, f"{confidence:.2f}", select_text
|
||||||
|
))
|
||||||
|
|
||||||
|
def select_all_watermarks(self):
|
||||||
|
"""选择所有水印区域"""
|
||||||
|
self.analyzer.selected_regions = self.analyzer.watermark_regions.copy()
|
||||||
|
self.update_watermark_tree()
|
||||||
|
self.display_image()
|
||||||
|
|
||||||
|
def deselect_all_watermarks(self):
|
||||||
|
"""取消选择所有水印区域"""
|
||||||
|
self.analyzer.selected_regions = []
|
||||||
|
self.update_watermark_tree()
|
||||||
|
self.display_image()
|
||||||
|
|
||||||
|
def apply_watermark_selection(self):
|
||||||
|
"""应用水印选择(基于用户在表格中的选择)"""
|
||||||
|
selected_regions = []
|
||||||
|
|
||||||
|
for item_id in self.watermark_tree.selection():
|
||||||
|
item = self.watermark_tree.item(item_id)
|
||||||
|
values = item['values']
|
||||||
|
|
||||||
|
if not values:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# 查找对应的区域
|
||||||
|
for region in self.analyzer.watermark_regions:
|
||||||
|
x, y, w, h = region['position']
|
||||||
|
if (int(values[1]) == x and int(values[2]) == y and
|
||||||
|
int(values[3]) == w and int(values[4]) == h):
|
||||||
|
selected_regions.append(region)
|
||||||
|
break
|
||||||
|
|
||||||
|
self.analyzer.selected_regions = selected_regions
|
||||||
|
self.update_watermark_tree()
|
||||||
|
self.display_image()
|
||||||
|
|
||||||
|
def process_current_image(self):
|
||||||
|
"""处理当前图片,去除选中的水印"""
|
||||||
|
if self.current_image is None:
|
||||||
|
messagebox.showinfo("提示", "请先加载图片")
|
||||||
|
return
|
||||||
|
|
||||||
|
if not self.analyzer.selected_regions:
|
||||||
|
messagebox.showinfo("提示", "请先选择要去除的水印区域")
|
||||||
|
return
|
||||||
|
|
||||||
|
if self.processing:
|
||||||
|
messagebox.showinfo("提示", "正在处理图片,请稍候...")
|
||||||
|
return
|
||||||
|
|
||||||
|
# 更新修复参数
|
||||||
|
self.update_repair_parameters()
|
||||||
|
|
||||||
|
# 禁用处理按钮
|
||||||
|
self.processing = True
|
||||||
|
self.status_bar.config(text="处理中...")
|
||||||
|
|
||||||
|
# 在单独的线程中执行图片处理
|
||||||
|
process_thread = threading.Thread(target=self._process_current_image_thread)
|
||||||
|
process_thread.daemon = True
|
||||||
|
process_thread.start()
|
||||||
|
|
||||||
|
def _process_current_image_thread(self):
|
||||||
|
"""在后台线程中处理当前图片"""
|
||||||
|
try:
|
||||||
|
# 处理图片
|
||||||
|
result = self.analyzer.remove_watermarks(self.current_image)
|
||||||
|
|
||||||
|
# 保存结果
|
||||||
|
if self.output_folder:
|
||||||
|
output_path = os.path.join(self.output_folder,
|
||||||
|
f"processed_{os.path.basename(self.image_files[self.current_index])}")
|
||||||
|
cv2.imwrite(output_path, result)
|
||||||
|
self.status_bar.config(text=f"已保存处理后的图片: {output_path}")
|
||||||
|
else:
|
||||||
|
self.status_bar.config(text="处理完成,但未指定输出文件夹,结果未保存")
|
||||||
|
|
||||||
|
# 更新当前图片为处理后的结果
|
||||||
|
self.current_image = result
|
||||||
|
|
||||||
|
# 更新UI
|
||||||
|
self.root.after(0, self.display_image)
|
||||||
|
self.root.after(0, self._reset_processing_state)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self.root.after(0, messagebox.showerror, "错误", f"处理图片时出错: {str(e)}")
|
||||||
|
self.root.after(0, self._reset_processing_state)
|
||||||
|
|
||||||
|
def _reset_processing_state(self):
|
||||||
|
"""重置处理状态"""
|
||||||
|
self.processing = False
|
||||||
|
self.status_bar.config(text="就绪")
|
||||||
|
|
||||||
|
def process_all_images(self):
|
||||||
|
"""批量处理所有图片"""
|
||||||
|
if not self.image_files:
|
||||||
|
messagebox.showinfo("提示", "请先加载图片")
|
||||||
|
return
|
||||||
|
|
||||||
|
if not self.output_folder:
|
||||||
|
messagebox.showinfo("提示", "请先选择输出文件夹")
|
||||||
|
return
|
||||||
|
|
||||||
|
if self.processing:
|
||||||
|
messagebox.showinfo("提示", "正在处理图片,请稍候...")
|
||||||
|
return
|
||||||
|
|
||||||
|
# 更新修复参数
|
||||||
|
self.update_repair_parameters()
|
||||||
|
|
||||||
|
# 确认对话框
|
||||||
|
result = messagebox.askyesno("确认", f"确定要批量处理所有 {len(self.image_files)} 张图片吗?")
|
||||||
|
if not result:
|
||||||
|
return
|
||||||
|
|
||||||
|
# 禁用处理按钮
|
||||||
|
self.processing = True
|
||||||
|
self.status_bar.config(text="批量处理中...")
|
||||||
|
|
||||||
|
# 在单独的线程中执行批量处理
|
||||||
|
batch_thread = threading.Thread(target=self._batch_process_thread)
|
||||||
|
batch_thread.daemon = True
|
||||||
|
batch_thread.start()
|
||||||
|
|
||||||
|
def _batch_process_thread(self, i=None):
|
||||||
|
"""在后台线程中执行批量处理"""
|
||||||
|
try:
|
||||||
|
total = len(self.image_files)
|
||||||
|
processed = 0
|
||||||
|
errors = 0
|
||||||
|
|
||||||
|
for i, img_path in enumerate(self.image_files):
|
||||||
|
# 更新状态栏
|
||||||
|
self.root.after(0, lambda i=i: self.status_bar.config(text=f"处理中 ({i + 1}/{total})..."))
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 读取图片
|
||||||
|
img = cv2.imread(img_path)
|
||||||
|
if img is None:
|
||||||
|
errors += 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
# 分析水印
|
||||||
|
self.analyzer.analyze(img)
|
||||||
|
|
||||||
|
# 处理图片
|
||||||
|
if self.analyzer.watermark_regions:
|
||||||
|
result = self.analyzer.remove_watermarks(img)
|
||||||
|
|
||||||
|
# 保存结果
|
||||||
|
output_path = os.path.join(self.output_folder,
|
||||||
|
f"processed_{os.path.basename(img_path)}")
|
||||||
|
cv2.imwrite(output_path, result)
|
||||||
|
|
||||||
|
processed += 1
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"处理图片 {img_path} 时出错: {str(e)}")
|
||||||
|
errors += 1
|
||||||
|
|
||||||
|
# 更新UI
|
||||||
|
self.root.after(0, lambda: messagebox.showinfo("完成",
|
||||||
|
f"批量处理完成!\n成功: {processed}\n失败: {errors}"))
|
||||||
|
self.root.after(0, lambda: self.status_bar.config(text=f"批量处理完成: 成功 {processed}, 失败 {errors}"))
|
||||||
|
self.root.after(0, self._reset_processing_state)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self.root.after(0, messagebox.showerror, "错误", f"批量处理时出错: {str(e)}")
|
||||||
|
self.root.after(0, self._reset_processing_state)
|
||||||
|
|
||||||
|
def update_analysis_parameters(self):
|
||||||
|
"""更新分析参数"""
|
||||||
|
self.analyzer.text_threshold = self.text_sensitivity.get()
|
||||||
|
self.analyzer.stamp_threshold = self.stamp_sensitivity.get()
|
||||||
|
self.analyzer.saliency_threshold = self.saliency_threshold.get()
|
||||||
|
self.analyzer.edge_threshold = self.edge_threshold.get()
|
||||||
|
self.analyzer.use_color_filter = self.use_color_filter.get()
|
||||||
|
self.analyzer.use_mser = self.use_mser.get()
|
||||||
|
self.analyzer.use_texture_analysis = self.use_texture_analysis.get()
|
||||||
|
|
||||||
|
def update_repair_parameters(self):
|
||||||
|
"""更新修复参数"""
|
||||||
|
self.analyzer.inpaint_radius = self.repair_radius.get()
|
||||||
|
|
||||||
|
if self.repair_algorithm.get() == "TELEA":
|
||||||
|
self.analyzer.inpaint_algorithm = cv2.INPAINT_TELEA
|
||||||
|
else:
|
||||||
|
self.analyzer.inpaint_algorithm = cv2.INPAINT_NS
|
||||||
|
|
||||||
|
def on_text_sensitivity_change(self, event):
|
||||||
|
"""文字敏感度滑块变化事件"""
|
||||||
|
value = self.text_sensitivity.get()
|
||||||
|
self.text_sensitivity_value.config(text=str(value))
|
||||||
|
|
||||||
|
def on_stamp_sensitivity_change(self, event):
|
||||||
|
"""印章敏感度滑块变化事件"""
|
||||||
|
value = self.stamp_sensitivity.get()
|
||||||
|
self.stamp_sensitivity_value.config(text=str(value))
|
||||||
|
|
||||||
|
def on_edge_threshold_change(self, event):
|
||||||
|
"""边缘阈值滑块变化事件"""
|
||||||
|
value = self.edge_threshold.get()
|
||||||
|
self.edge_value.config(text=str(value))
|
||||||
|
|
||||||
|
def on_repair_radius_change(self, event):
|
||||||
|
"""修复半径滑块变化事件"""
|
||||||
|
value = self.repair_radius.get()
|
||||||
|
self.repair_radius_value.config(text=str(value))
|
||||||
|
|
||||||
|
def on_repair_algorithm_change(self, event):
|
||||||
|
"""修复算法选择变化事件"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def on_detection_method_change(self):
|
||||||
|
"""检测方法选择变化事件"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def on_mousewheel(self, event):
|
||||||
|
"""鼠标滚轮事件处理"""
|
||||||
|
if event.num == 4 or event.delta > 0: # 向上滚动
|
||||||
|
self.canvas.yview_scroll(-1, "units")
|
||||||
|
elif event.num == 5 or event.delta < 0: # 向下滚动
|
||||||
|
self.canvas.yview_scroll(1, "units")
|
||||||
|
|
||||||
|
def on_canvas_configure(self, event):
|
||||||
|
"""画布大小变化事件处理"""
|
||||||
|
self.display_image()
|
||||||
|
|
||||||
|
|
||||||
|
def run_gui():
|
||||||
|
"""运行GUI应用"""
|
||||||
|
# 尝试导入Analyzer类
|
||||||
|
try:
|
||||||
|
from analyzer import WatermarkAnalyzer
|
||||||
|
analyzer = WatermarkAnalyzer()
|
||||||
|
except ImportError:
|
||||||
|
# 如果无法导入,创建一个模拟类用于演示
|
||||||
|
class WatermarkAnalyzer:
|
||||||
|
def __init__(self):
|
||||||
|
self.watermark_regions = []
|
||||||
|
self.selected_regions = []
|
||||||
|
self.inpaint_radius = 5
|
||||||
|
self.inpaint_algorithm = cv2.INPAINT_TELEA
|
||||||
|
self.text_threshold = 180
|
||||||
|
self.stamp_threshold = 180
|
||||||
|
self.saliency_threshold = 0.5
|
||||||
|
self.edge_threshold = 100
|
||||||
|
self.use_color_filter = True
|
||||||
|
self.use_mser = True
|
||||||
|
self.use_texture_analysis = True
|
||||||
|
|
||||||
|
def analyze(self, image):
|
||||||
|
# 模拟水印分析
|
||||||
|
height, width = image.shape[:2]
|
||||||
|
self.watermark_regions = [
|
||||||
|
{
|
||||||
|
'type': 'text',
|
||||||
|
'position': (width // 2 - 50, 10, 100, 30),
|
||||||
|
'confidence': 0.8,
|
||||||
|
'contour': None
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'type': 'stamp',
|
||||||
|
'position': (width - 100, height - 100, 80, 80),
|
||||||
|
'confidence': 0.9,
|
||||||
|
'contour': None
|
||||||
|
}
|
||||||
|
]
|
||||||
|
self.selected_regions = self.watermark_regions.copy()
|
||||||
|
|
||||||
|
def remove_watermarks(self, image):
|
||||||
|
# 模拟水印去除
|
||||||
|
result = image.copy()
|
||||||
|
for region in self.selected_regions:
|
||||||
|
x, y, w, h = region['position']
|
||||||
|
# 创建掩码
|
||||||
|
mask = np.zeros((image.shape[0], image.shape[1]), dtype=np.uint8)
|
||||||
|
mask[y:y + h, x:x + w] = 255
|
||||||
|
# 使用修复算法去除水印
|
||||||
|
result = cv2.inpaint(result, mask, self.inpaint_radius, self.inpaint_algorithm)
|
||||||
|
return result
|
||||||
|
|
||||||
|
analyzer = WatermarkAnalyzer()
|
||||||
|
|
||||||
|
# 创建主窗口
|
||||||
|
root = tk.Tk()
|
||||||
|
root.title("图片水印分析与去除工具 V9")
|
||||||
|
root.geometry("1000x800")
|
||||||
|
|
||||||
|
# 设置样式
|
||||||
|
style = ttk.Style()
|
||||||
|
style.configure('Accent.TButton', font=(None, 10, 'bold'))
|
||||||
|
|
||||||
|
# 创建GUI
|
||||||
|
app = WatermarkGUI(root, analyzer)
|
||||||
|
|
||||||
|
# 启动主循环
|
||||||
|
root.mainloop()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
run_gui()
|
||||||
24
main.py
Normal file
24
main.py
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import os
|
||||||
|
import sys
|
||||||
|
from tkinter import Tk
|
||||||
|
from gui import WatermarkGUI
|
||||||
|
from analyzer import WatermarkAnalyzer
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
# 创建配置目录
|
||||||
|
config_dir = "config"
|
||||||
|
if not os.path.exists(config_dir):
|
||||||
|
os.makedirs(config_dir)
|
||||||
|
|
||||||
|
# 初始化水印分析器
|
||||||
|
analyzer = WatermarkAnalyzer()
|
||||||
|
|
||||||
|
# 创建GUI
|
||||||
|
root = Tk()
|
||||||
|
app = WatermarkGUI(root, analyzer)
|
||||||
|
root.mainloop()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
63
utils.py
Normal file
63
utils.py
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
import os
|
||||||
|
import cv2
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
|
||||||
|
def create_output_folder(source_folder, suffix="_processed"):
|
||||||
|
"""创建输出文件夹"""
|
||||||
|
base_name = os.path.basename(source_folder)
|
||||||
|
parent_dir = os.path.dirname(source_folder)
|
||||||
|
output_folder = os.path.join(parent_dir, f"{base_name}{suffix}")
|
||||||
|
|
||||||
|
if not os.path.exists(output_folder):
|
||||||
|
os.makedirs(output_folder)
|
||||||
|
|
||||||
|
return output_folder
|
||||||
|
|
||||||
|
|
||||||
|
def is_image_file(file_path):
|
||||||
|
"""检查文件是否为图片"""
|
||||||
|
valid_extensions = ['.jpg', '.jpeg', '.png', '.bmp', '.tiff', '.gif']
|
||||||
|
ext = os.path.splitext(file_path)[1].lower()
|
||||||
|
return ext in valid_extensions
|
||||||
|
|
||||||
|
|
||||||
|
def resize_to_fit(image, max_width, max_height):
|
||||||
|
"""调整图片大小以适应指定尺寸"""
|
||||||
|
height, width = image.shape[:2]
|
||||||
|
|
||||||
|
# 计算宽高比
|
||||||
|
ratio = min(max_width / width, max_height / height)
|
||||||
|
|
||||||
|
# 计算新尺寸
|
||||||
|
new_width = int(width * ratio)
|
||||||
|
new_height = int(height * ratio)
|
||||||
|
|
||||||
|
# 调整大小
|
||||||
|
return cv2.resize(image, (new_width, new_height))
|
||||||
|
|
||||||
|
|
||||||
|
def draw_watermark_regions(image, regions):
|
||||||
|
"""在图片上绘制水印区域"""
|
||||||
|
result = image.copy()
|
||||||
|
|
||||||
|
for region in regions:
|
||||||
|
x, y, w, h = region['position']
|
||||||
|
|
||||||
|
# 根据区域类型选择颜色
|
||||||
|
if region['type'] == 'text':
|
||||||
|
color = (255, 0, 0) # 蓝色
|
||||||
|
elif region['type'] == 'stamp':
|
||||||
|
color = (0, 0, 255) # 红色
|
||||||
|
else:
|
||||||
|
color = (0, 255, 0) # 绿色
|
||||||
|
|
||||||
|
# 绘制矩形
|
||||||
|
cv2.rectangle(result, (x, y), (x + w, y + h), color, 2)
|
||||||
|
|
||||||
|
# 添加标签
|
||||||
|
label = f"{region['type']} ({region['confidence']:.2f})"
|
||||||
|
cv2.putText(result, label, (x, y - 10),
|
||||||
|
cv2.FONT_HERSHEY_SIMPLEX, 0.5, color, 1)
|
||||||
|
|
||||||
|
return result
|
||||||
1
watermark_config.json
Normal file
1
watermark_config.json
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"position": [205, 289, 1469, 1784], "timestamp": "2025-05-22T19:49:32.415955", "inpaint_radius": 5, "inpaint_algorithm": 1, "dilate_size": 3, "use_adaptive_mask": true, "text_threshold": 180, "min_text_area": 100, "text_color_range": [[50, 50, 50], [180, 180, 180]], "stamp_color_range": [[0, 0, 100], [100, 100, 255]]}
|
||||||
Reference in New Issue
Block a user