mirror of
https://github.com/Theodor-Springmann-Stiftung/kgpz_web.git
synced 2025-10-29 09:05:30 +00:00
291 lines
10 KiB
Python
Executable File
291 lines
10 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""
|
|
Interactive Parameter Tuning Tool for Newspaper Image Cleaning
|
|
|
|
This tool helps you find optimal parameters for your specific images
|
|
by providing an interactive tuning interface.
|
|
"""
|
|
|
|
import cv2
|
|
import json
|
|
import numpy as np
|
|
from pathlib import Path
|
|
from image_cleaner import NewspaperImageCleaner
|
|
|
|
|
|
class ParameterTuner:
|
|
"""Interactive parameter tuning for image cleaning pipeline."""
|
|
|
|
def __init__(self, sample_image_path):
|
|
"""Initialize with a sample image for tuning."""
|
|
self.original = cv2.imread(str(sample_image_path))
|
|
if self.original is None:
|
|
raise ValueError(f"Could not load image: {sample_image_path}")
|
|
|
|
# Resize large images for faster processing during tuning
|
|
height, width = self.original.shape[:2]
|
|
if height > 1500 or width > 1500:
|
|
scale = min(1500/height, 1500/width)
|
|
new_width = int(width * scale)
|
|
new_height = int(height * scale)
|
|
self.original = cv2.resize(self.original, (new_width, new_height))
|
|
print(f"Resized image to {new_width}x{new_height} for faster tuning")
|
|
|
|
self.current_params = self._get_default_params()
|
|
self.cleaner = NewspaperImageCleaner(self.current_params)
|
|
|
|
def _get_default_params(self):
|
|
"""Get default parameters as starting point."""
|
|
return {
|
|
'bilateral_d': 9,
|
|
'bilateral_sigma_color': 75,
|
|
'bilateral_sigma_space': 75,
|
|
'clahe_clip_limit': 2.0,
|
|
'clahe_grid_size': (8, 8),
|
|
'gamma': 1.2,
|
|
'denoise_h': 10,
|
|
'morph_kernel_size': 2,
|
|
'unsharp_amount': 1.5,
|
|
'unsharp_radius': 1.0,
|
|
'unsharp_threshold': 0,
|
|
}
|
|
|
|
def update_parameter(self, param_name, value):
|
|
"""Update a single parameter and refresh the cleaner."""
|
|
if param_name in self.current_params:
|
|
# Handle special cases
|
|
if param_name == 'clahe_grid_size':
|
|
self.current_params[param_name] = (int(value), int(value))
|
|
else:
|
|
self.current_params[param_name] = value
|
|
|
|
# Update cleaner with new parameters
|
|
self.cleaner = NewspaperImageCleaner(self.current_params)
|
|
print(f"Updated {param_name} = {value}")
|
|
|
|
def process_with_current_params(self, steps=None):
|
|
"""Process the sample image with current parameters."""
|
|
if steps is None:
|
|
steps = ['denoise', 'contrast', 'background', 'sharpen']
|
|
|
|
image = self.original.copy()
|
|
|
|
# Apply processing steps
|
|
if 'denoise' in steps:
|
|
image = self.cleaner.reduce_noise(image)
|
|
|
|
if 'contrast' in steps:
|
|
image = self.cleaner.enhance_contrast(image)
|
|
|
|
if 'background' in steps:
|
|
image = self.cleaner.clean_background(image)
|
|
|
|
if 'sharpen' in steps:
|
|
image = self.cleaner.sharpen_image(image)
|
|
|
|
return image
|
|
|
|
def create_comparison(self, steps=None):
|
|
"""Create side-by-side comparison with current parameters."""
|
|
processed = self.process_with_current_params(steps)
|
|
|
|
# Create side-by-side comparison
|
|
height = max(self.original.shape[0], processed.shape[0])
|
|
comparison = np.hstack([
|
|
cv2.resize(self.original, (self.original.shape[1], height)),
|
|
cv2.resize(processed, (processed.shape[1], height))
|
|
])
|
|
|
|
return comparison
|
|
|
|
def save_comparison(self, output_path, steps=None):
|
|
"""Save comparison image to file."""
|
|
comparison = self.create_comparison(steps)
|
|
cv2.imwrite(str(output_path), comparison)
|
|
print(f"Comparison saved to: {output_path}")
|
|
|
|
def save_config(self, config_path):
|
|
"""Save current parameters to JSON config file."""
|
|
# Convert tuple to list for JSON serialization
|
|
config_to_save = self.current_params.copy()
|
|
if 'clahe_grid_size' in config_to_save:
|
|
config_to_save['clahe_grid_size'] = list(config_to_save['clahe_grid_size'])
|
|
|
|
with open(config_path, 'w') as f:
|
|
json.dump(config_to_save, f, indent=4)
|
|
print(f"Configuration saved to: {config_path}")
|
|
|
|
def load_config(self, config_path):
|
|
"""Load parameters from JSON config file."""
|
|
with open(config_path, 'r') as f:
|
|
loaded_params = json.load(f)
|
|
|
|
# Convert list back to tuple if needed
|
|
if 'clahe_grid_size' in loaded_params:
|
|
loaded_params['clahe_grid_size'] = tuple(loaded_params['clahe_grid_size'])
|
|
|
|
self.current_params.update(loaded_params)
|
|
self.cleaner = NewspaperImageCleaner(self.current_params)
|
|
print(f"Configuration loaded from: {config_path}")
|
|
|
|
def interactive_tune(self):
|
|
"""Start interactive tuning session."""
|
|
print("\n" + "="*60)
|
|
print("INTERACTIVE PARAMETER TUNING")
|
|
print("="*60)
|
|
print("Commands:")
|
|
print(" set <param> <value> - Set parameter value")
|
|
print(" show - Show current parameters")
|
|
print(" test [steps] - Test current parameters")
|
|
print(" save <file> - Save configuration to file")
|
|
print(" load <file> - Load configuration from file")
|
|
print(" compare [file] - Save comparison image")
|
|
print(" presets - Show parameter presets")
|
|
print(" help - Show this help")
|
|
print(" quit - Exit tuning")
|
|
print("\nParameters you can adjust:")
|
|
for param in self.current_params:
|
|
print(f" {param}")
|
|
|
|
while True:
|
|
try:
|
|
command = input("\ntuner> ").strip().split()
|
|
if not command:
|
|
continue
|
|
|
|
cmd = command[0].lower()
|
|
|
|
if cmd == 'quit' or cmd == 'exit':
|
|
break
|
|
|
|
elif cmd == 'show':
|
|
self._show_parameters()
|
|
|
|
elif cmd == 'set' and len(command) >= 3:
|
|
param = command[1]
|
|
try:
|
|
value = float(command[2]) if '.' in command[2] else int(command[2])
|
|
except ValueError:
|
|
value = command[2]
|
|
self.update_parameter(param, value)
|
|
|
|
elif cmd == 'test':
|
|
steps = command[1:] if len(command) > 1 else None
|
|
print("Processing with current parameters...")
|
|
processed = self.process_with_current_params(steps)
|
|
print(f"Processed image shape: {processed.shape}")
|
|
|
|
elif cmd == 'save' and len(command) > 1:
|
|
self.save_config(command[1])
|
|
|
|
elif cmd == 'load' and len(command) > 1:
|
|
self.load_config(command[1])
|
|
|
|
elif cmd == 'compare':
|
|
output = command[1] if len(command) > 1 else "tuning_comparison.jpg"
|
|
self.save_comparison(output)
|
|
|
|
elif cmd == 'presets':
|
|
self._show_presets()
|
|
|
|
elif cmd == 'help':
|
|
self._show_help()
|
|
|
|
else:
|
|
print("Unknown command. Type 'help' for available commands.")
|
|
|
|
except KeyboardInterrupt:
|
|
print("\nExiting tuner...")
|
|
break
|
|
except Exception as e:
|
|
print(f"Error: {str(e)}")
|
|
|
|
def _show_parameters(self):
|
|
"""Display current parameter values."""
|
|
print("\nCurrent Parameters:")
|
|
print("-" * 30)
|
|
for param, value in self.current_params.items():
|
|
print(f" {param:<20} = {value}")
|
|
|
|
def _show_presets(self):
|
|
"""Show preset configurations for different image types."""
|
|
presets = {
|
|
"light_cleaning": {
|
|
"bilateral_d": 5,
|
|
"denoise_h": 5,
|
|
"clahe_clip_limit": 1.5,
|
|
"gamma": 1.1,
|
|
"unsharp_amount": 1.2
|
|
},
|
|
"heavy_cleaning": {
|
|
"bilateral_d": 15,
|
|
"denoise_h": 15,
|
|
"clahe_clip_limit": 3.0,
|
|
"gamma": 1.3,
|
|
"unsharp_amount": 2.0
|
|
},
|
|
"high_contrast": {
|
|
"clahe_clip_limit": 4.0,
|
|
"gamma": 1.4,
|
|
"unsharp_amount": 2.5
|
|
}
|
|
}
|
|
|
|
print("\nAvailable Presets:")
|
|
print("-" * 30)
|
|
for name, params in presets.items():
|
|
print(f"{name}:")
|
|
for param, value in params.items():
|
|
print(f" {param} = {value}")
|
|
print()
|
|
|
|
def _show_help(self):
|
|
"""Show detailed help information."""
|
|
help_text = """
|
|
Parameter Descriptions:
|
|
-----------------------
|
|
bilateral_d : Neighborhood diameter for bilateral filtering (5-15)
|
|
bilateral_sigma_color: Filter sigma in color space (50-150)
|
|
bilateral_sigma_space: Filter sigma in coordinate space (50-150)
|
|
clahe_clip_limit : Contrast limit for CLAHE (1.0-4.0)
|
|
clahe_grid_size : CLAHE tile grid size (4-16)
|
|
gamma : Gamma correction value (0.8-2.0)
|
|
denoise_h : Denoising filter strength (5-20)
|
|
morph_kernel_size : Morphological operation kernel size (1-5)
|
|
unsharp_amount : Unsharp masking amount (0.5-3.0)
|
|
unsharp_radius : Unsharp masking radius (0.5-2.0)
|
|
unsharp_threshold : Unsharp masking threshold (0-10)
|
|
|
|
Tips:
|
|
- Start with small adjustments (±20% of current value)
|
|
- Test frequently with 'compare' command
|
|
- Save working configurations before major changes
|
|
- Use 'test denoise' to test individual steps
|
|
"""
|
|
print(help_text)
|
|
|
|
|
|
def main():
|
|
"""Main function for command line usage."""
|
|
import argparse
|
|
|
|
parser = argparse.ArgumentParser(description="Interactive parameter tuning for newspaper image cleaning")
|
|
parser.add_argument("image", help="Sample image path for tuning")
|
|
parser.add_argument("-c", "--config", help="Load initial config from file")
|
|
|
|
args = parser.parse_args()
|
|
|
|
try:
|
|
tuner = ParameterTuner(args.image)
|
|
|
|
if args.config:
|
|
tuner.load_config(args.config)
|
|
|
|
tuner.interactive_tune()
|
|
|
|
except Exception as e:
|
|
print(f"Error: {str(e)}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main() |