AutoPercenty3/test/img_test2/modules/ocr.py

517 lines
25 KiB
Python

import os
import sys
import requests
import logging
from paddleocr import PaddleOCR
from PIL import Image
from io import BytesIO
from translatepy import Translator # 번역 기능을 위한 라이브러리
import time
import json
class ChineseImageOCRProcessor():
"""
PaddleOCR을 이용하여 이미지 내 중국어 텍스트를 인식하는 클래스.
주요 기능:
- 이미지 URL 다운로드 및 유효성 검사
- OCR 처리 후 이미지 내에 중국어 글자가 존재하는지 판단
- 수평 텍스트(옵션 적용)만 추출하여 텍스트로 리턴
- 인자로 전달받은 단어 목록의 키워드가 인식된 텍스트 내에 포함되어 있는지 확인
- 인식된 텍스트에서 개별 중국어 글자 목록을 추출
"""
def __init__(self, logger, toggle_states, use_gpu=False, det_enabled=True, horizontal_only=True):
"""
초기화 메서드.
:param use_gpu: GPU 사용 여부 (기본 False)
:param det_enabled: Detection 사용 여부 (True이면 det + rec 모델 사용)
:param horizontal_only: 수평 텍스트만 필터링할지 여부 (True일 경우)
"""
self.logger = logger
self.toggle_states = toggle_states
self.use_gpu = use_gpu
self.det_enabled = det_enabled
self.horizontal_only = horizontal_only
# 모델 디렉토리 설정
self.base_dir = self.get_base_dir()
self.rec_model_dir = os.path.join(self.base_dir, "ppocr", "PP_Models", "rec")
self.det_model_dir = os.path.join(self.base_dir, "ppocr", "PP_Models", "det")
self.cls_model_dir = os.path.join(self.base_dir, "ppocr", "PP_Models", "cls")
self.logger.log(f"모델 디렉토리: {self.base_dir}", level=logging.DEBUG)
self.logger.log(f"rec_model_dir: {self.rec_model_dir}", level=logging.DEBUG)
self.logger.log(f"det_model_dir: {self.det_model_dir}", level=logging.DEBUG)
self.logger.log(f"cls_model_dir: {self.cls_model_dir}", level=logging.DEBUG)
# PaddleOCR 초기화
self.ocr = self.initialize_ocr()
# HTTP 요청 헤더 설정
self.headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
"AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/88.0.4324.150 Safari/537.36",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,"
"image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9",
"Accept-Language": "en-US,en;q=0.9",
"Accept-Encoding": "gzip, deflate, br",
"DNT": "1",
"Connection": "keep-alive",
"Upgrade-Insecure-Requests": "1",
"Cache-Control": "max-age=0"
}
self.keywords_list = self.create_keywords_list()
@staticmethod
def get_base_dir():
"""
실행 환경에 따라 base_dir을 설정하는 메서드.
cx_Freeze로 패키징된 경우 실행 파일의 경로, 일반 Python 환경일 경우 __file__을 기준으로 설정.
"""
if getattr(sys, 'frozen', False): # 패키징된 경우
base_dir = os.path.dirname(sys.executable)
internal_dir = os.path.join(base_dir, 'lib', 'src') # lib 디렉토리 포함
if os.path.exists(internal_dir): # lib 디렉토리가 존재하면 base_dir로 설정
return internal_dir
else: # 일반 Python 실행 환경
base_dir = os.path.dirname(os.path.abspath(__file__))
debug_dir = os.path.join(base_dir, 'src') # lib 디렉토리 포함
return debug_dir
def initialize_ocr(self):
"""
PaddleOCR 초기화. det_enabled 옵션에 따라 Detection 모델 사용 여부 결정.
"""
if self.det_enabled:
ocr = PaddleOCR(
use_gpu=self.use_gpu,
use_angle_cls=True, # 텍스트 방향 분류 활성화
lang="ch",
det_model_dir=self.det_model_dir,
rec_model_dir=self.rec_model_dir,
cls_model_dir=self.cls_model_dir
)
else:
ocr = PaddleOCR(
use_gpu=self.use_gpu,
use_angle_cls=True,
lang="ch",
det_model_dir=None, # Detection 비활성화
rec_model_dir=self.rec_model_dir,
cls_model_dir=self.cls_model_dir
)
return ocr
def is_valid_image(self, image_data):
"""
다운로드한 이미지 데이터의 유효성을 검사합니다.
(너비, 높이가 최소 10픽셀 이상인지 확인)
:param image_data: 이미지 바이너리 데이터
:return: 유효하면 True, 아니면 False
"""
try:
img = Image.open(BytesIO(image_data))
width, height = img.size
return width > 10 and height > 10
except Exception as e:
self.logger.log(f"이미지 검증 실패: {e}" , level=logging.ERROR, exc_info=True)
return False
def contains_chinese_text(self, image_data):
"""
이미지 데이터 내에 중국어 문자(유니코드 범위 \u4e00 ~ \u9fff)가 포함되어 있는지 확인합니다.
:param image_data: 이미지 바이너리 데이터
:return: 포함되어 있으면 True, 아니면 False
"""
try:
results = self.ocr.ocr(image_data)
if not results or not results[0]:
return False
for line in results[0]:
text = line[1][0]
if any('\u4e00' <= char <= '\u9fff' for char in text):
return True
return False
except Exception as e:
self.logger.log(f"중국어 포함 여부 확인 실패: {e}" , level=logging.ERROR, exc_info=True)
# OCR primitive 에러만 raise
raise
def filter_horizontal_text(self, results):
"""
OCR 결과에서 텍스트의 방향(각도)이 수평(0도 혹은 360도에 가까운 경우)인 것만 필터링합니다.
:param results: OCR 결과
:return: 수평 텍스트 리스트 (각 항목: (텍스트, 신뢰도, 각도))
"""
horizontal_texts = []
for line in results[0]:
text = line[1][0]
confidence = line[1][1]
angle = line[2]
if abs(angle) < 10 or abs(angle - 360) < 10:
horizontal_texts.append((text, confidence, angle))
return horizontal_texts
def extract_text(self, image_data):
"""
OCR을 사용해 이미지로부터 텍스트를 추출합니다.
horizontal_only 옵션이 True인 경우 수평 상태의 텍스트만 추출합니다.
:param image_data: 이미지 바이너리 데이터
:return: 추출된 텍스트 리스트
"""
try:
results = self.ocr.ocr(image_data)
if not results or not results[0]:
return []
if self.horizontal_only:
filtered = self.filter_horizontal_text(results)
extracted_text = [item[0] for item in filtered]
else:
extracted_text = [line[1][0] for line in results[0]]
return extracted_text
except Exception as e:
self.logger.log(f"OCR 처리 실패: {e}" , level=logging.ERROR, exc_info=True)
return []
def check_keywords_in_text(self, text, keywords):
"""
주어진 텍스트 내에 인자로 전달받은 단어 목록의 단어가 포함되어 있는지 확인합니다.
:param text: OCR로 추출한 텍스트
:param keywords: 확인할 단어 목록 (리스트)
:return: 포함된 단어 리스트 (포함되지 않으면 빈 리스트)
"""
found_keywords = []
for keyword in keywords:
if keyword in text:
found_keywords.append(keyword)
return found_keywords
def contains_unwanted_text(self, unwanted_keywords, text):
"""
추출된 텍스트에 불필요한 키워드가 포함되어 있는지 확인합니다.
Args:
unwanted_keywords (list): 필터링할 불필요한 키워드 리스트
text (str or list): 확인할 텍스트 (문자열 또는 문자열 리스트)
Returns:
bool: 불필요한 키워드를 포함하면 True, 아니면 False
"""
if not unwanted_keywords or not text:
return False
# text가 리스트인 경우 각 요소를 개별적으로 검사
if isinstance(text, list):
text_list = text
else:
text_list = [str(text)]
# 각 텍스트 요소에 대해 불필요한 키워드 검사
for text_item in text_list:
if not text_item:
continue
text_str = str(text_item).strip()
if not text_str:
continue
# 불필요한 키워드가 텍스트에 포함되어 있는지 확인
for keyword in unwanted_keywords:
if keyword and keyword.strip() in text_str:
self.logger.log(f"불필요한 단어 '{keyword}' 발견되어 처리 제외: {text_str}", level=logging.INFO)
return True
return False
def translate_text(self, text, target_language="ko"):
"""
translatepy를 사용하여 인식된 중국어 텍스트를 한국어로 번역합니다.
:param text: 번역할 원본 텍스트 (중국어)
:param target_language: 번역 대상 언어 (기본 "ko")
:return: 번역된 텍스트 (한국어), 번역 실패 시 원본 텍스트 반환
"""
try:
translator = Translator()
result = translator.translate(text, target_language)
translated_text = result.result
self.logger.log(f"번역된 텍스트: {translated_text}", level=logging.INFO)
return translated_text
except Exception as e:
self.logger.log(f"번역 실패: {e}", level=logging.ERROR, exc_info=True)
return text
def extract_chinese_characters(self, text):
"""
주어진 텍스트에서 개별 중국어 문자를 리스트로 반환합니다.
:param text: OCR로 추출된 텍스트
:return: 중국어 문자 리스트
"""
return [char for char in text if '\u4e00' <= char <= '\u9fff']
def create_keywords_list(self):
"""
toggle_states에서 unwanted_words를 가져와 모든 키워드를 단일 리스트로 만듭니다.
Returns:
list: 한국어, 중국어, combined 키워드가 모두 합쳐진 리스트
"""
# unwanted_words 가져오기 (이미 mainUI_SP.py에서 로드됨)
unwanted_words = self.toggle_states.get('unwanted_words', {})
# 이미 딕셔너리 형태로 로드되어 있는 경우
if isinstance(unwanted_words, dict):
self.logger.log(f"unwanted_words 로드 완료: {unwanted_words}", level=logging.DEBUG)
else:
# JSON 문자열인 경우 파싱 시도
try:
if isinstance(unwanted_words, str) and unwanted_words.startswith('{'):
unwanted_words = json.loads(unwanted_words)
self.logger.log(f"unwanted_words JSON 파싱 완료: {unwanted_words}", level=logging.DEBUG)
else:
unwanted_words = {}
self.logger.log("unwanted_words가 올바른 형식이 아닙니다. 빈 딕셔너리를 사용합니다.", level=logging.WARNING)
except Exception as e:
self.logger.log(f"unwanted_words JSON 파싱 중 오류 발생: {str(e)}", level=logging.ERROR)
unwanted_words = {}
if not unwanted_words:
self.logger.log("unwanted_words가 설정되지 않았습니다. 빈 리스트를 반환합니다.", level=logging.WARNING)
return []
keywords_list = []
# 한국어 키워드 추가
if 'korean' in unwanted_words and isinstance(unwanted_words['korean'], list):
keywords_list.extend(unwanted_words['korean'])
# 중국어 키워드 추가
if 'chinese' in unwanted_words and isinstance(unwanted_words['chinese'], list):
keywords_list.extend(unwanted_words['chinese'])
# combined 키워드 추가
if 'combined' in unwanted_words and isinstance(unwanted_words['combined'], list):
keywords_list.extend(unwanted_words['combined'])
# 중복 제거 및 공백 제거
keywords_list = list(set(keyword.strip() for keyword in keywords_list if keyword and keyword.strip()))
self.logger.log(f"생성된 불필요한 키워드 리스트: {keywords_list}", level=logging.DEBUG)
return keywords_list
# def check_image_in_zhcn(self, image_urls, unwanted_keywords=None):
# """
# 이미지 URL 리스트를 처리하여 각 이미지가 중국어 텍스트를 포함하는지 확인하고,
# 불필요한 키워드를 포함하는지 확인합니다.
# Args:
# image_urls (str or list): 확인할 이미지 URL 또는 URL 리스트
# unwanted_keywords (dict): {'korean': [...], 'chinese': [...], 'combined': [...]} 형태의 딕셔너리
# Returns:
# dict: {url: should_translate} 형태의 딕셔너리
# should_translate = True: 번역 대상 (중국어 텍스트 포함이고 불필요한 키워드 없음)
# should_translate = False: 중국어 텍스트가 없는 경우
# url이 results에서 제외됨: 중국어 텍스트가 있지만 불필요한 키워드가 포함된 경우
# """
# if isinstance(image_urls, str):
# image_urls = [image_urls]
# results = {}
# for url in image_urls:
# try:
# # 이미지 다운로드
# image_data = self.download_image(url)
# if not image_data or not self.is_valid_image(image_data):
# self.logger.log(f"이미지 다운로드 실패 또는 유효하지 않은 이미지: {url}", level=logging.WARNING)
# results[url] = False
# continue
# # 중국어 텍스트 포함 여부 확인 - 없으면 False
# if not self.contains_chinese_text(image_data):
# self.logger.log(f"중국어 텍스트가 없는 이미지: {url}", level=logging.INFO)
# results[url] = False
# continue
# # 여기까지 왔다면 중국어 텍스트는 있는 상태
# # 텍스트 추출하여 불필요한 키워드 확인
# text_list = self.extract_text(image_data)
# if self.keywords_list and self.contains_unwanted_text(self.keywords_list, text_list):
# self.logger.log(f"불필요한 단어가 포함된 이미지입니다: {url}", level=logging.INFO)
# # 중국어 텍스트가 있지만 불필요한 키워드가 있으면 results에 포함하지 않음
# continue
# # 중국어 텍스트가 있고 불필요한 키워드가 없는 경우만 True
# results[url] = True
# except Exception as e:
# self.logger.log(f"이미지 확인 중 오류 발생: {url}, 에러: {str(e)}", level=logging.ERROR, exc_info=True)
# results[url] = False
# return results
def check_local_image_zhcn(self, image_path):
"""
로컬 이미지 파일을 처리하여 중국어 텍스트 포함 여부와 불필요한 키워드 포함 여부를 확인합니다.
Args:
image_path (str): 확인할 로컬 이미지 파일 경로
Returns:
dict: 다음 키를 포함하는 딕셔너리
- 'status': 'has_chinese_clean', 'has_chinese_unwanted', 'no_chinese', 'error' 중 하나
- 'message': 상태에 대한 설명 메시지
- 'extracted_text': 추출된 텍스트 (성공한 경우에만)
상태 설명:
- 'has_chinese_clean': 중국어 텍스트가 있고 불필요한 키워드가 없음 (번역 대상)
- 'has_chinese_unwanted': 중국어 텍스트가 있지만 불필요한 키워드가 포함됨 (번역 제외)
- 'no_chinese': 중국어 텍스트가 없음
- 'error': 처리 중 오류 발생
"""
try:
# 이미지 파일 존재 여부 확인
if not os.path.exists(image_path):
self.logger.log(f"이미지 파일이 존재하지 않습니다: {image_path}", level=logging.ERROR)
return {
'status': 'error',
'message': f'이미지 파일이 존재하지 않습니다: {image_path}'
}
# 이미지 파일 읽기
try:
with open(image_path, 'rb') as f:
image_data = f.read()
except Exception as e:
self.logger.log(f"이미지 파일 읽기 실패: {image_path}, 에러: {str(e)}", level=logging.ERROR)
return {
'status': 'error',
'message': f'이미지 파일 읽기 실패: {str(e)}'
}
# 이미지 유효성 검사
if not self.is_valid_image(image_data):
self.logger.log(f"유효하지 않은 이미지 파일: {image_path}", level=logging.WARNING)
return {
'status': 'error',
'message': '유효하지 않은 이미지 파일입니다'
}
# 중국어 텍스트 포함 여부 확인
if not self.contains_chinese_text(image_data):
self.logger.log(f"중국어 텍스트가 없는 이미지: {image_path}", level=logging.INFO)
return {
'status': 'no_chinese',
'message': '중국어 텍스트가 포함되어 있지 않습니다'
}
# 여기까지 왔다면 중국어 텍스트는 있는 상태
# 텍스트 추출하여 불필요한 키워드 확인
text_list = self.extract_text(image_data)
if self.keywords_list and self.contains_unwanted_text(self.keywords_list, text_list):
self.logger.log(f"불필요한 단어가 포함된 이미지입니다: {image_path}", level=logging.INFO)
return {
'status': 'has_chinese_unwanted',
'message': '중국어 텍스트가 있지만 불필요한 키워드가 포함되어 있습니다',
'extracted_text': text_list
}
# 중국어 텍스트가 있고 불필요한 키워드가 없는 경우
self.logger.log(f"번역 대상 이미지: {image_path}", level=logging.INFO)
return {
'status': 'has_chinese_clean',
'message': '중국어 텍스트가 있고 불필요한 키워드가 없습니다 (번역 대상)',
'extracted_text': text_list
}
except Exception as e:
self.logger.log(f"이미지 확인 중 오류 발생: {image_path}, 에러: {str(e)}", level=logging.ERROR, exc_info=True)
return {
'status': 'error',
'message': f'이미지 처리 중 오류 발생: {str(e)}'
}
# def main():
# from loggerModule import Logger
# logger = Logger()
# # 클래스 인스턴스 생성 (옵션에 따라 GPU, Detection, 수평 텍스트 여부 설정)
# ocr_processor = ChineseImageOCRProcessor(logger=logger, use_gpu=False, det_enabled=True, horizontal_only=False)
# # 테스트용 이미지 URL (중국어 텍스트가 포함된 이미지)
# # test_image_url1 = "https://img.alicdn.com/imgextra/i3/1865984740/O1CN01xjUF5n1ksyDXRUzZx_!!1865984740.jpg_Q75.jpg"
# # test_image_url2 = "https://img.alicdn.com/imgextra/i2/50536882/O1CN0191hQW320i0e3eIR0U_!!50536882.png"
# # test_image_url3 = "https://img.alicdn.com/imgextra/i3/570013073/O1CN01L0BpDF1YZU8Nqevgi_!!570013073.jpg"
# test_image_url4 = "https://img.alicdn.com/imgextra/i3/363859269/O1CN01TzzWWm2ILFvQczR9l_!!363859269.jpg"
# test_image_urls = ['https://img.alicdn.com/imgextra/i3/363859269/O1CN01TzzWWm2ILFvQczR9l_!!363859269.jpg',
# 'https://img.alicdn.com/imgextra/i3/363859269/O1CN01dzKZTY2ILFvRgBsCQ_!!363859269.jpg',
# 'https://img.alicdn.com/imgextra/i2/363859269/O1CN010ZBa5O2ILFvNO9g0q_!!363859269.jpg',
# 'https://img.alicdn.com/imgextra/i3/363859269/O1CN01RuBoa82ILFvUjrXre_!!363859269.jpg',
# 'https://img.alicdn.com/imgextra/i2/363859269/O1CN01bFCo3m2ILFvUjrseh_!!363859269.jpg',
# 'https://file.percenty.co.kr/public/652bed8e865b1f32ea62bf1f/products/67f6dc0e2997204031b43179/e4ca0b07-58d7-4e32-9695-c7fd5f4cd73e.jpg',
# 'https://file.percenty.co.kr/public/652bed8e865b1f32ea62bf1f/products/67f6dc0e2997204031b43179/6bdc771c-5f98-4bf1-b3d2-69bd9e93f0be.jpg',
# 'https://file.percenty.co.kr/public/652bed8e865b1f32ea62bf1f/products/67f6dc0e2997204031b43179/23719d47-a97c-4ce3-aa38-9229c4d44b0c.jpg',
# 'https://file.percenty.co.kr/public/652bed8e865b1f32ea62bf1f/products/67f6dc0e2997204031b43179/24df49a4-f56d-407a-a36e-e3bbfc2c51db.jpg',
# 'https://file.percenty.co.kr/public/652bed8e865b1f32ea62bf1f/products/67f6dc0e2997204031b43179/567388f8-9e16-4c22-9de8-90ad1f47230f.jpg',
# 'https://file.percenty.co.kr/public/652bed8e865b1f32ea62bf1f/products/67f6dc0e2997204031b43179/f5ad63ac-94ee-4aea-9084-b655fb5b1008.jpg',
# 'https://img.alicdn.com/imgextra/i3/O1CN01MsWlBa29wVnysxUvn_!!6000000008132-2-tps-750-880.png']
# # 키워드 목록 예시 (확인하고자 하는 단어 목록)
# # keywords = ["优惠", "免费", "促销", "价格说明", "承诺", "终身", "的网络", "금메달", "위안", "할인", "평생", "水磨片", "价格说明"]
# keywords = {'korean': ['할인', '무료', '증정', '이벤트', '특가', '세일', '사은품', '보증', '품절', '행사', '할인가', '무료배송', '물레방아', '가격설명'], 'chinese': ['折扣', '免费', '赠品', '活动', '特价', '促销', '赠品', '保证', '售罄', '活动', '折扣价', '免费配送', '水磨片', '价格说明'], 'combined': ['할인(折扣)', '무료(免费)', '증정(赠品)', '이벤트(活动)', '특가(特价)', '세일(促销)', '사은품(赠品)', '보증(保证)', '품절(售罄)', '행사(活动)', '할인가(折扣价)', '무료배송(免费配送)', '물레방아(水磨片)', '가격설명(价格说明)']}
# start_time = time.time()
# # 이미지 처리 및 결과 확인
# result = ocr_processor.check_image_in_zhcn(test_image_urls, keywords)
# print(f"결과: {result}")
# print(f"처리 시간: {time.time() - start_time}초")
# # # OCR 결과를 한국어로 번역
# # translated_text = processor.translate_text(recognized_text)
# # # 중국어 텍자 존재 여부 (OCR 결과가 존재하면 True)
# # chinese_text_exists = True if recognized_text else False
# # # 인식된 텍스트 내 개별 중국어 글자 추출
# # # chinese_char_list = processor.extract_chinese_characters(recognized_text) if recognized_text else []
# # print("중국어 글자 존재 여부:", chinese_text_exists)
# # # print("인식된 중국어 글자 목록:", chinese_char_list)
# # # 추가적으로 전체 인식 텍스트 출력 (있을 경우)
# # if recognized_text:
# # print("최종 인식 텍스트:", recognized_text)
# # print("번역된 텍스트:", translated_text)
# # else:
# # print("이미지 내에 중국어 텍스트가 없거나 처리에 실패하였습니다.")
# # contains_unwanted_text = processor.contains_unwanted_text(keywords, recognized_text)
# # print("불필요한 중국어 단어 포함 여부:", contains_unwanted_text)
# # contains_unwanted_text = processor.contains_unwanted_text(keywords, translated_text)
# # print("불필요한 한국어 단어 포함 여부:", contains_unwanted_text)
# if __name__ == "__main__":
# main()