AutoPercenty3/test/img_test2/backup/modules/output_module.py

593 lines
24 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# -*- coding: utf-8 -*-
"""
출력 모듈 - 번역 결과를 시각적으로 비교하고 출력
원본, 인페인팅, 최종 결과를 나란히 배치하여 한눈에 볼 수 있게 합니다.
"""
import cv2
import numpy as np
from typing import List, Dict, Any, Tuple, Optional
import os
from datetime import datetime
import json
class OutputModule:
def __init__(self):
"""출력 모듈 초기화"""
self.output_history = []
print("출력 모듈 초기화 완료")
def create_comparison_output(self, original_path: str, inpainted_image: np.ndarray,
final_image: np.ndarray, output_dir: str,
inpainting_method: str = "unknown") -> str:
"""
원본, 인페인팅, 최종 결과를 비교하는 출력 이미지 생성
Args:
original_path (str): 원본 이미지 경로
inpainted_image (np.ndarray): 인페인팅된 이미지
final_image (np.ndarray): 최종 번역 결과 이미지
output_dir (str): 출력 디렉토리
inpainting_method (str): 사용된 인페인팅 방법
Returns:
str: 생성된 비교 이미지 경로
"""
# 원본 이미지 로드
original_image = cv2.imread(original_path)
if original_image is None:
print(f"원본 이미지를 읽을 수 없습니다: {original_path}")
return None
# 출력 디렉토리 생성
os.makedirs(output_dir, exist_ok=True)
# 비교 이미지 생성
comparison_image = self._create_three_panel_comparison(
original_image, inpainted_image, final_image, inpainting_method
)
# 파일명 생성 (타임스탬프 포함)
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
original_name = os.path.splitext(os.path.basename(original_path))[0]
# 개별 이미지 저장
original_img_path = os.path.join(output_dir, f"{original_name}_{inpainting_method}_{timestamp}_original.jpg")
inpainted_img_path = os.path.join(output_dir, f"{original_name}_{inpainting_method}_{timestamp}_inpainted.jpg")
final_img_path = os.path.join(output_dir, f"{original_name}_{inpainting_method}_{timestamp}_final.jpg")
cv2.imwrite(original_img_path, original_image)
cv2.imwrite(inpainted_img_path, inpainted_image)
cv2.imwrite(final_img_path, final_image)
# 비교 이미지 저장 (기존처럼)
output_filename = f"{original_name}_{inpainting_method}_{timestamp}_comparison.jpg"
output_path = os.path.join(output_dir, output_filename)
cv2.imwrite(output_path, comparison_image)
# 메타데이터 저장
self._save_metadata(output_dir, output_filename, {
'original_path': original_path,
'inpainting_method': inpainting_method,
'timestamp': timestamp,
'output_path': output_path,
'original_img_path': original_img_path,
'inpainted_img_path': inpainted_img_path,
'final_img_path': final_img_path
})
# 히스토리에 추가
self.output_history.append({
'timestamp': timestamp,
'original_path': original_path,
'output_path': output_path,
'inpainting_method': inpainting_method,
'original_img_path': original_img_path,
'inpainted_img_path': inpainted_img_path,
'final_img_path': final_img_path
})
print(f"개별 이미지 저장 완료: {original_img_path}, {inpainted_img_path}, {final_img_path}")
print(f"비교 이미지 생성 완료: {output_path}")
return output_path
def _create_three_panel_comparison(self, original: np.ndarray, inpainted: np.ndarray,
final: np.ndarray, method: str) -> np.ndarray:
"""
3개 패널 비교 이미지 생성
Args:
original (np.ndarray): 원본 이미지
inpainted (np.ndarray): 인페인팅된 이미지
final (np.ndarray): 최종 결과 이미지
method (str): 인페인팅 방법
Returns:
np.ndarray: 비교 이미지
"""
# 이미지 크기 통일
target_height = 400
# 원본 이미지 크기에 맞춰 너비 계산
original_height, original_width = original.shape[:2]
target_width = int(target_height * original_width / original_height)
# 각 이미지 리사이즈
original_resized = cv2.resize(original, (target_width, target_height))
inpainted_resized = cv2.resize(inpainted, (target_width, target_height))
final_resized = cv2.resize(final, (target_width, target_height))
# 비교 이미지 캔버스 생성
panel_spacing = 10
header_height = 60
footer_height = 40
total_width = target_width * 3 + panel_spacing * 2
total_height = target_height + header_height + footer_height
comparison = np.ones((total_height, total_width, 3), dtype=np.uint8) * 255
# 헤더 영역 (제목)
cv2.rectangle(comparison, (0, 0), (total_width, header_height), (240, 240, 240), -1)
# 제목 텍스트
title = f"Image Translation Result - Method: {method.upper()}"
title_size = cv2.getTextSize(title, cv2.FONT_HERSHEY_SIMPLEX, 0.8, 2)[0]
title_x = (total_width - title_size[0]) // 2
cv2.putText(comparison, title, (title_x, 35),
cv2.FONT_HERSHEY_SIMPLEX, 0.8, (50, 50, 50), 2)
# 이미지 배치
y_offset = header_height
# 원본 이미지
x1 = 0
comparison[y_offset:y_offset+target_height, x1:x1+target_width] = original_resized
# 인페인팅된 이미지
x2 = target_width + panel_spacing
comparison[y_offset:y_offset+target_height, x2:x2+target_width] = inpainted_resized
# 최종 결과 이미지
x3 = target_width * 2 + panel_spacing * 2
comparison[y_offset:y_offset+target_height, x3:x3+target_width] = final_resized
# 라벨 추가
labels = ["Original", "Inpainted", "Final Result"]
label_positions = [x1, x2, x3]
for label, x_pos in zip(labels, label_positions):
# 라벨 배경
label_bg_y = y_offset + target_height + 5
cv2.rectangle(comparison,
(x_pos, label_bg_y),
(x_pos + target_width, label_bg_y + 30),
(220, 220, 220), -1)
# 라벨 텍스트
label_size = cv2.getTextSize(label, cv2.FONT_HERSHEY_SIMPLEX, 0.6, 2)[0]
label_x = x_pos + (target_width - label_size[0]) // 2
cv2.putText(comparison, label, (label_x, label_bg_y + 20),
cv2.FONT_HERSHEY_SIMPLEX, 0.6, (50, 50, 50), 2)
# 구분선 추가
line_color = (200, 200, 200)
cv2.line(comparison, (target_width + panel_spacing//2, header_height),
(target_width + panel_spacing//2, y_offset + target_height), line_color, 2)
cv2.line(comparison, (target_width*2 + panel_spacing + panel_spacing//2, header_height),
(target_width*2 + panel_spacing + panel_spacing//2, y_offset + target_height), line_color, 2)
return comparison
def create_detailed_output(self, original_path: str, ocr_results: List[Dict],
translated_texts: List[str], inpainted_image: np.ndarray,
final_image: np.ndarray, output_dir: str,
inpainting_method: str) -> str:
"""
상세 정보가 포함된 출력 생성
Args:
original_path (str): 원본 이미지 경로
ocr_results (List[Dict]): OCR 결과
translated_texts (List[str]): 번역 결과
inpainted_image (np.ndarray): 인페인팅된 이미지
final_image (np.ndarray): 최종 결과
output_dir (str): 출력 디렉토리
inpainting_method (str): 인페인팅 방법
Returns:
str: 생성된 상세 출력 경로
"""
# 기본 비교 이미지 생성
comparison_path = self.create_comparison_output(
original_path, inpainted_image, final_image, output_dir, inpainting_method
)
# 상세 정보 이미지 생성
detail_image = self._create_detail_panel(ocr_results, translated_texts, inpainting_method)
# 전체 이미지 결합
comparison_image = cv2.imread(comparison_path)
combined_image = self._combine_comparison_and_detail(comparison_image, detail_image)
# 상세 출력 저장
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
original_name = os.path.splitext(os.path.basename(original_path))[0]
detail_filename = f"{original_name}_{inpainting_method}_{timestamp}_detailed.jpg"
detail_path = os.path.join(output_dir, detail_filename)
cv2.imwrite(detail_path, combined_image)
print(f"상세 출력 생성 완료: {detail_path}")
return detail_path
def _create_detail_panel(self, ocr_results: List[Dict], translated_texts: List[str],
method: str) -> np.ndarray:
"""
상세 정보 패널 생성
Args:
ocr_results (List[Dict]): OCR 결과
translated_texts (List[str]): 번역 결과
method (str): 인페인팅 방법
Returns:
np.ndarray: 상세 정보 패널 이미지
"""
# 패널 크기 계산
panel_width = 400
line_height = 25
header_height = 40
padding = 20
num_texts = len(ocr_results)
panel_height = header_height + (num_texts * line_height * 3) + padding * 2
# 패널 생성
panel = np.ones((panel_height, panel_width, 3), dtype=np.uint8) * 250
# 헤더
cv2.rectangle(panel, (0, 0), (panel_width, header_height), (200, 200, 200), -1)
cv2.putText(panel, "Translation Details", (10, 25),
cv2.FONT_HERSHEY_SIMPLEX, 0.7, (50, 50, 50), 2)
# 방법 정보
method_text = f"Method: {method}"
cv2.putText(panel, method_text, (10, header_height + 20),
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (100, 100, 100), 1)
# 번역 정보
y_offset = header_height + 45
for i, (ocr_result, translated_text) in enumerate(zip(ocr_results, translated_texts)):
# 원본 텍스트
original_text = ocr_result['text']
confidence = ocr_result['confidence']
# 텍스트가 너무 길면 줄임
if len(original_text) > 30:
original_text = original_text[:30] + "..."
if len(translated_text) > 30:
translated_text = translated_text[:30] + "..."
# 번호
cv2.putText(panel, f"{i+1}.", (10, y_offset),
cv2.FONT_HERSHEY_SIMPLEX, 0.4, (0, 0, 0), 1)
# 원본 텍스트
cv2.putText(panel, f"CN: {original_text}", (30, y_offset),
cv2.FONT_HERSHEY_SIMPLEX, 0.4, (0, 0, 150), 1)
# 번역된 텍스트
cv2.putText(panel, f"KR: {translated_text}", (30, y_offset + 15),
cv2.FONT_HERSHEY_SIMPLEX, 0.4, (150, 0, 0), 1)
# 신뢰도
cv2.putText(panel, f"Conf: {confidence:.2f}", (30, y_offset + 30),
cv2.FONT_HERSHEY_SIMPLEX, 0.3, (100, 100, 100), 1)
y_offset += line_height * 3 + 10
# 구분선
if i < len(ocr_results) - 1:
cv2.line(panel, (20, y_offset - 5), (panel_width - 20, y_offset - 5),
(220, 220, 220), 1)
return panel
def _combine_comparison_and_detail(self, comparison: np.ndarray,
detail: np.ndarray) -> np.ndarray:
"""비교 이미지와 상세 정보 패널 결합"""
comp_height, comp_width = comparison.shape[:2]
detail_height, detail_width = detail.shape[:2]
# 높이 맞추기
if comp_height > detail_height:
# 상세 패널 높이 늘리기
extended_detail = np.ones((comp_height, detail_width, 3), dtype=np.uint8) * 250
extended_detail[:detail_height, :] = detail
detail = extended_detail
elif detail_height > comp_height:
# 비교 이미지 높이 늘리기
extended_comparison = np.ones((detail_height, comp_width, 3), dtype=np.uint8) * 255
extended_comparison[:comp_height, :] = comparison
comparison = extended_comparison
# 수평으로 결합
combined = np.hstack([comparison, detail])
return combined
def _save_metadata(self, output_dir: str, filename: str, metadata: Dict[str, Any]):
"""메타데이터를 JSON 파일로 저장"""
metadata_filename = os.path.splitext(filename)[0] + "_metadata.json"
metadata_path = os.path.join(output_dir, metadata_filename)
with open(metadata_path, 'w', encoding='utf-8') as f:
json.dump(metadata, f, ensure_ascii=False, indent=2)
def create_batch_summary(self, output_dir: str, batch_results: List[Dict]) -> str:
"""
일괄 처리 결과 요약 생성
Args:
output_dir (str): 출력 디렉토리
batch_results (List[Dict]): 일괄 처리 결과
Returns:
str: 요약 보고서 경로
"""
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
summary_filename = f"batch_summary_{timestamp}.html"
summary_path = os.path.join(output_dir, summary_filename)
# HTML 보고서 생성
html_content = self._generate_html_summary(batch_results)
with open(summary_path, 'w', encoding='utf-8') as f:
f.write(html_content)
print(f"일괄 처리 요약 생성 완료: {summary_path}")
return summary_path
def _generate_html_summary(self, batch_results: List[Dict]) -> str:
"""
일괄 처리 결과의 HTML 요약 생성
Args:
batch_results (List[Dict]): 일괄 처리 결과 리스트
Returns:
str: HTML 내용
"""
html = """
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Batch Translation Summary</title>
<style>
body { font-family: Arial, sans-serif; margin: 20px; background: #f5f5f5; }
.header { background: #333; color: white; padding: 20px; text-align: center; }
.summary { background: white; padding: 20px; margin: 20px 0; border-radius: 5px; }
.result-item { background: white; margin: 10px 0; padding: 15px; border-radius: 5px; border-left: 4px solid #ccc; }
.result-item.success { border-left-color: #4CAF50; }
.result-item.error { border-left-color: #f44336; }
.method-tag { background: #2196F3; color: white; padding: 2px 8px; border-radius: 3px; font-size: 12px; }
</style>
</head>
<body>
<div class="header">
<h1>Batch Translation Summary</h1>
<p>Generated: {timestamp}</p>
</div>
<div class="summary">
<h2>Overall Statistics</h2>
<p>Total Images: {total_count}</p>
<p>Successful: {success_count}</p>
<p>Failed: {failed_count}</p>
</div>
""".format(
timestamp=datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
total_count=len(batch_results),
success_count=sum(1 for r in batch_results if r.get('success', False)),
failed_count=sum(1 for r in batch_results if not r.get('success', False))
)
# 각 결과 항목 추가
for i, result in enumerate(batch_results):
status_class = "success" if result.get('success', False) else "error"
method = result.get('method', 'unknown')
html += f"""
<div class="result-item {status_class}">
<h3>Image {i+1}: {os.path.basename(result.get('original_path', 'Unknown'))}</h3>
<span class="method-tag">{method}</span>
<p><strong>Status:</strong> {'Success' if result.get('success', False) else 'Failed'}</p>
<p><strong>Processing Time:</strong> {result.get('processing_time', 'N/A')}</p>
<p><strong>Detected Texts:</strong> {result.get('text_count', 0)}</p>
</div>
"""
html += """
</body>
</html>
"""
return html
def create_slideshow(self, output_dir: str, image_paths: List[str]) -> str:
"""
결과 이미지들의 슬라이드쇼 HTML 생성
Args:
output_dir (str): 출력 디렉토리
image_paths (List[str]): 이미지 경로 리스트
Returns:
str: 슬라이드쇼 HTML 파일 경로
"""
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
slideshow_filename = f"slideshow_{timestamp}.html"
slideshow_path = os.path.join(output_dir, slideshow_filename)
# 상대 경로로 변환
relative_paths = [os.path.relpath(path, output_dir) for path in image_paths]
html_content = f"""
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Translation Results Slideshow</title>
<style>
body {{ font-family: Arial, sans-serif; margin: 0; padding: 20px; background: #f5f5f5; }}
.slideshow-container {{ position: relative; max-width: 1000px; margin: auto; background: white; }}
.slide {{ display: none; text-align: center; padding: 20px; }}
.slide.active {{ display: block; }}
.slide img {{ max-width: 100%; height: auto; }}
.prev, .next {{ cursor: pointer; position: absolute; top: 50%; width: auto; margin-top: -22px; padding: 16px; color: white; font-weight: bold; font-size: 18px; background: rgba(0,0,0,0.5); border: none; user-select: none; }}
.next {{ right: 0; }}
.prev:hover, .next:hover {{ background: rgba(0,0,0,0.8); }}
.dots-container {{ text-align: center; margin-top: 20px; }}
.dot {{ cursor: pointer; height: 15px; width: 15px; margin: 0 5px; background-color: #bbb; border-radius: 50%; display: inline-block; }}
.dot.active, .dot:hover {{ background-color: #717171; }}
.slide-counter {{ position: absolute; top: 10px; right: 10px; background: rgba(0,0,0,0.7); color: white; padding: 5px 10px; border-radius: 3px; }}
</style>
</head>
<body>
<h1 style="text-align: center;">Translation Results Slideshow</h1>
<div class="slideshow-container">
"""
# 슬라이드 추가
for i, path in enumerate(relative_paths):
active_class = "active" if i == 0 else ""
html_content += f"""
<div class="slide {active_class}">
<div class="slide-counter">{i+1} / {len(relative_paths)}</div>
<img src="{path}" alt="Translation Result {i+1}">
</div>
"""
html_content += """
<button class="prev" onclick="changeSlide(-1)"></button>
<button class="next" onclick="changeSlide(1)"></button>
</div>
<div class="dots-container">
"""
# 도트 네비게이션 추가
for i in range(len(relative_paths)):
active_class = "active" if i == 0 else ""
html_content += f'<span class="dot {active_class}" onclick="currentSlide({i+1})"></span>'
html_content += """
</div>
<script>
let slideIndex = 1;
function changeSlide(n) {
showSlide(slideIndex += n);
}
function currentSlide(n) {
showSlide(slideIndex = n);
}
function showSlide(n) {
let slides = document.getElementsByClassName("slide");
let dots = document.getElementsByClassName("dot");
if (n > slides.length) {slideIndex = 1}
if (n < 1) {slideIndex = slides.length}
for (let i = 0; i < slides.length; i++) {
slides[i].classList.remove("active");
}
for (let i = 0; i < dots.length; i++) {
dots[i].classList.remove("active");
}
slides[slideIndex-1].classList.add("active");
dots[slideIndex-1].classList.add("active");
}
// 자동 슬라이드 (선택사항)
setInterval(function() {
changeSlide(1);
}, 10000); // 10초마다 자동 넘김
</script>
</body>
</html>
"""
with open(slideshow_path, 'w', encoding='utf-8') as f:
f.write(html_content)
print(f"슬라이드쇼 생성 완료: {slideshow_path}")
return slideshow_path
def test_module(self):
"""출력 모듈 테스트"""
print("출력 모듈 테스트 시작...")
# 테스트 출력 디렉토리 생성
test_output_dir = "test_output"
os.makedirs(test_output_dir, exist_ok=True)
# 테스트 이미지들 생성
original_image = self._create_test_image("Original Image", (0, 100, 0))
inpainted_image = self._create_test_image("Inpainted Image", (100, 0, 0))
final_image = self._create_test_image("Final Result", (0, 0, 100))
# 테스트 이미지 저장
original_path = os.path.join(test_output_dir, "test_original.jpg")
cv2.imwrite(original_path, original_image)
# 기본 비교 출력 테스트
print("기본 비교 출력 테스트...")
comparison_path = self.create_comparison_output(
original_path, inpainted_image, final_image,
test_output_dir, "cv2_telea"
)
# 상세 출력 테스트
print("상세 출력 테스트...")
test_ocr_results = [
{'text': '测试文本1', 'confidence': 0.95},
{'text': '测试文本2', 'confidence': 0.88}
]
test_translations = ['테스트 텍스트1', '테스트 텍스트2']
detail_path = self.create_detailed_output(
original_path, test_ocr_results, test_translations,
inpainted_image, final_image, test_output_dir, "lama"
)
print("출력 모듈 테스트 완료!")
print(f"테스트 결과는 {test_output_dir} 폴더에 저장되었습니다.")
def _create_test_image(self, text: str, color: Tuple[int, int, int]) -> np.ndarray:
"""테스트용 이미지 생성"""
image = np.ones((300, 400, 3), dtype=np.uint8) * 255
# 배경색 적용
image[:, :] = color
# 텍스트 추가
text_size = cv2.getTextSize(text, cv2.FONT_HERSHEY_SIMPLEX, 1, 2)[0]
text_x = (400 - text_size[0]) // 2
text_y = (300 + text_size[1]) // 2
cv2.putText(image, text, (text_x, text_y),
cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 255, 255), 2)
return image
if __name__ == "__main__":
output_module = OutputModule()
output_module.test_module()