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()