技能备份 - 2026-04-15 (40个技能)
This commit is contained in:
@@ -0,0 +1,311 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Super OCR - Main entry point with multi-engine parallel support
|
||||
|
||||
Usage:
|
||||
python main.py --image path/to/image.png [--engine auto|tesseract|paddle|macvision|all]
|
||||
python main.py --images ./images/*.png [--output ./results]
|
||||
|
||||
Examples:
|
||||
# Auto mode (recommended) - runs all available engines on macOS
|
||||
python main.py --image screenshot.png
|
||||
|
||||
# Force Tesseract
|
||||
python main.py --image document.jpg --engine tesseract
|
||||
|
||||
# Force PaddleOCR (high accuracy Chinese)
|
||||
python main.py --image chinese_menu.png --engine paddle
|
||||
|
||||
# Force MacVision (macOS only)
|
||||
python main.py --image document.png --engine macvision
|
||||
|
||||
# Run all available engines (macOS: tesseract + paddle + macvision)
|
||||
python main.py --image complex_doc.png --engine all
|
||||
|
||||
# Batch mode with verbose output
|
||||
python main.py --images ./invoices/*.png --output ./results --verbose
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
from concurrent.futures import ThreadPoolExecutor, as_completed
|
||||
from pathlib import Path
|
||||
from typing import Dict, List, Optional
|
||||
|
||||
# Add parent to path for imports when running from skill directory
|
||||
sys.path.insert(0, str(Path(__file__).parent))
|
||||
|
||||
try:
|
||||
from engine.selector import select_engine, get_available_engines, select_best_result
|
||||
from engine.tesseract import TesseractOCR
|
||||
from engine.paddle import PaddleOCR
|
||||
from output_formatter import format_output
|
||||
from dependencies import check_all_dependencies
|
||||
except ImportError as e:
|
||||
print(f"[ERROR] Import failed: {e}")
|
||||
print("\n[INSTALL INSTRUCTIONS]")
|
||||
print("="*60)
|
||||
print("Missing dependencies detected. Please install:")
|
||||
print()
|
||||
print(" pip install paddleocr paddlepaddle pytesseract pillow opencv-python numpy")
|
||||
print()
|
||||
print("Or for macOS with Tesseract:")
|
||||
print()
|
||||
print(" brew install tesseract")
|
||||
print(" pip install paddleocr paddlepaddle pytesseract pillow opencv-python numpy")
|
||||
print()
|
||||
print("For other platforms, see: https://github.com/openclaw/super-ocr")
|
||||
print("="*60)
|
||||
sys.exit(1)
|
||||
|
||||
# Try to import MacVision if on macOS
|
||||
macvision_available = False
|
||||
if sys.platform == 'darwin':
|
||||
try:
|
||||
from engine.macvision import MacVisionOCR
|
||||
macvision_available = True
|
||||
print("[INFO] MacVision OCR available")
|
||||
except ImportError:
|
||||
print("[WARN] MacVision OCR not available (pip install pyobjc)")
|
||||
|
||||
class OCRProcessor:
|
||||
"""Main OCR processor with multi-engine parallel support"""
|
||||
|
||||
def __init__(self, engine: str = 'auto', verbose: bool = False):
|
||||
"""
|
||||
Initialize OCR processor.
|
||||
|
||||
Args:
|
||||
engine: 'auto', 'tesseract', 'paddle', 'macvision', or 'all'
|
||||
verbose: Enable detailed logging
|
||||
"""
|
||||
self.engine = engine.lower()
|
||||
self.verbose = verbose
|
||||
self.engines_to_use = []
|
||||
self.processors = {}
|
||||
|
||||
def _select_and_init(self, image_path: str) -> None:
|
||||
"""Select engines based on content and initialize all"""
|
||||
self.engines_to_use = select_engine(image_path, self.engine)
|
||||
|
||||
if self.verbose:
|
||||
print(f"[INFO] Using engines: {', '.join(self.engines_to_use)}")
|
||||
|
||||
# Initialize selected engines
|
||||
for eng in self.engines_to_use:
|
||||
try:
|
||||
if eng == 'tesseract':
|
||||
self.processors['tesseract'] = TesseractOCR(verbose=self.verbose)
|
||||
elif eng == 'paddle':
|
||||
self.processors['paddle'] = PaddleOCR(verbose=self.verbose)
|
||||
elif eng == 'macvision':
|
||||
if macvision_available:
|
||||
self.processors['macvision'] = MacVisionOCR(verbose=self.verbose)
|
||||
else:
|
||||
if self.verbose:
|
||||
print(f"[WARN] MacVision not available on this platform")
|
||||
else:
|
||||
if self.verbose:
|
||||
print(f"[WARN] Unknown engine: {eng}")
|
||||
except Exception as e:
|
||||
if self.verbose:
|
||||
print(f"[ERROR] Failed to initialize {eng}: {e}")
|
||||
|
||||
def extract_parallel(self, image_path: str) -> Dict:
|
||||
"""
|
||||
Extract text from image using multiple engines in parallel.
|
||||
|
||||
Args:
|
||||
image_path: Path to input image
|
||||
|
||||
Returns:
|
||||
Dict with text, confidence, selected_engine, average confidence, etc.
|
||||
"""
|
||||
if not self.processors:
|
||||
self._select_and_init(image_path)
|
||||
|
||||
if not self.processors:
|
||||
return {
|
||||
'text': '',
|
||||
'confidence': 0.0,
|
||||
'error': 'No engines available',
|
||||
'processing_time_ms': 0
|
||||
}
|
||||
|
||||
start_time = time.time()
|
||||
results = []
|
||||
|
||||
def run_engine(engine_name: str, processor):
|
||||
try:
|
||||
result = processor.extract(image_path)
|
||||
result['engine'] = engine_name
|
||||
return result
|
||||
except Exception as e:
|
||||
return {
|
||||
'engine': engine_name,
|
||||
'text': '',
|
||||
'confidence': 0.0,
|
||||
'error': str(e),
|
||||
'processing_time_ms': 0
|
||||
}
|
||||
|
||||
# Run engines in parallel
|
||||
with ThreadPoolExecutor(max_workers=len(self.processors)) as executor:
|
||||
futures = {
|
||||
executor.submit(run_engine, name, proc): name
|
||||
for name, proc in self.processors.items()
|
||||
}
|
||||
|
||||
for future in as_completed(futures):
|
||||
result = future.result()
|
||||
results.append(result)
|
||||
|
||||
if self.verbose:
|
||||
engine_name = result.get('engine', 'unknown')
|
||||
if result.get('error'):
|
||||
print(f"[{engine_name}] Error: {result['error']}")
|
||||
else:
|
||||
print(f"[{engine_name}] Confidence: {result.get('confidence', 0):.2%}, Time: {result.get('processing_time_ms', 0):.2f}ms")
|
||||
|
||||
# Select best result using agent择优 logic
|
||||
best = select_best_result(
|
||||
results,
|
||||
preferred_engine='paddle' # PaddleOCR preferred for Chinese
|
||||
)
|
||||
|
||||
# Add summary info
|
||||
best['processing_time_ms'] = round((time.time() - start_time) * 1000, 2)
|
||||
best['total_engines'] = len(results)
|
||||
best['engines_used'] = self.engines_to_use
|
||||
|
||||
return best
|
||||
|
||||
def extract(self, image_path: str) -> Dict:
|
||||
"""
|
||||
Extract text from image (legacy single-engine mode).
|
||||
|
||||
Args:
|
||||
image_path: Path to input image
|
||||
|
||||
Returns:
|
||||
Dict with text, confidence, engine, timing info
|
||||
"""
|
||||
return self.extract_parallel(image_path)
|
||||
|
||||
def batch_extract(self, image_paths: List[str]) -> List[Dict]:
|
||||
"""Process multiple images"""
|
||||
return [self.extract(path) for path in image_paths]
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description='Super OCR - Multi-engine parallel text extraction with intelligent selection'
|
||||
)
|
||||
|
||||
# Input options
|
||||
input_group = parser.add_mutually_exclusive_group(required=True)
|
||||
input_group.add_argument('--image', help='Single image file to process')
|
||||
input_group.add_argument('--images', nargs='+', help='Multiple image files')
|
||||
|
||||
# Engine selection
|
||||
parser.add_argument(
|
||||
'--engine',
|
||||
choices=['auto', 'tesseract', 'paddle', 'macvision', 'all'],
|
||||
default='auto',
|
||||
help='OCR engine(s) to use (default: auto)'
|
||||
)
|
||||
|
||||
# Output options
|
||||
parser.add_argument(
|
||||
'--output', '-o',
|
||||
help='Output directory for results (default: stdout)'
|
||||
)
|
||||
parser.add_argument(
|
||||
'--format',
|
||||
choices=['text', 'json', 'structured'],
|
||||
default='json',
|
||||
help='Output format (default: json)'
|
||||
)
|
||||
parser.add_argument(
|
||||
'--verbose', '-v',
|
||||
action='store_true',
|
||||
help='Enable verbose output'
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# Check dependencies
|
||||
if args.engine in ['auto', 'paddle', 'all']:
|
||||
check_all_dependencies(interactive=False)
|
||||
|
||||
# Warn about macOS
|
||||
if sys.platform != 'darwin' and args.engine in ['macvision', 'all']:
|
||||
print("[WARN] MacVision only available on macOS")
|
||||
|
||||
# Create processor
|
||||
processor = OCRProcessor(
|
||||
engine=args.engine,
|
||||
verbose=args.verbose
|
||||
)
|
||||
|
||||
# Process images
|
||||
if args.image:
|
||||
image_paths = [args.image]
|
||||
else:
|
||||
# Expand glob patterns
|
||||
image_paths = []
|
||||
for pattern in args.images:
|
||||
image_paths.extend(sorted(Path().glob(pattern)))
|
||||
image_paths = [str(p) for p in image_paths]
|
||||
|
||||
if args.verbose:
|
||||
print(f"\n[INFO] Processing {len(image_paths)} image(s)")
|
||||
|
||||
# Extract text
|
||||
results = processor.batch_extract(image_paths)
|
||||
|
||||
# Format and output
|
||||
output_func = format_output(args.format)
|
||||
|
||||
if args.output:
|
||||
# Save to files
|
||||
output_path = Path(args.output)
|
||||
output_path.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
for image_path, result in zip(image_paths, results):
|
||||
stem = Path(image_path).stem
|
||||
output_file = output_path / f"{stem}_ocr.json"
|
||||
|
||||
with open(output_file, 'w', encoding='utf-8') as f:
|
||||
json.dump(result, f, ensure_ascii=False, indent=2)
|
||||
|
||||
if args.verbose:
|
||||
print(f"[OK] Saved: {output_file}")
|
||||
else:
|
||||
# Print to stdout
|
||||
for image_path, result in zip(image_paths, results):
|
||||
print(f"\n{'='*60}")
|
||||
print(f"File: {image_path}")
|
||||
print(f"Selected Engine: {result.get('selected_engine', 'unknown')}")
|
||||
print(f"Confidence: {result.get('confidence', 0):.2%}")
|
||||
print(f"Total Engines: {result.get('total_engines', 1)}")
|
||||
print(f"Processes: {', '.join(result.get('engines_used', []))}")
|
||||
print(f"Time: {result.get('processing_time_ms', 0):.2f}ms")
|
||||
print(f"{'='*60}")
|
||||
|
||||
if result.get('error'):
|
||||
print(f"[ERROR] {result['error']}")
|
||||
else:
|
||||
print(output_func(result))
|
||||
|
||||
# Summary
|
||||
total_time = sum(r.get('processing_time_ms', 0) for r in results)
|
||||
if args.verbose and len(results) > 1:
|
||||
print(f"\n[INFO] Processed {len(results)} images in {total_time:.2f}ms")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in New Issue
Block a user