diff --git a/.gitignore b/.gitignore index 33e7851..faeeb38 100644 --- a/.gitignore +++ b/.gitignore @@ -174,3 +174,4 @@ ipython_config.py # Remove previous ipynb_checkpoints # git rm -r .ipynb_checkpoints/ +image_ex/ \ No newline at end of file diff --git a/descriptor.py b/descriptor.py new file mode 100644 index 0000000..8dddccf --- /dev/null +++ b/descriptor.py @@ -0,0 +1,170 @@ +from openai import OpenAI +from dotenv import load_dotenv +import os +import base64 +from pathlib import Path +from PIL import Image +import requests +from io import BytesIO +import json +import re + +# .env 로드 +load_dotenv() +client = OpenAI(api_key=os.getenv("OPENAI_API_KEY")) + +# GPT-4o mini 단가 (USD per 1M tokens) +INPUT_COST_PER_TOKEN = 0.15 / 1_000_000 +OUTPUT_COST_PER_TOKEN = 0.60 / 1_000_000 +BASE_IMAGE_TOKENS_512 = 2833 # 512x512 기준 추정 +USD_TO_KRW = 1400 # 환율 + +#def parse_gpt_json_response(response_text: str): +# """ +# GPT 모델이 반환한 코드 블록 형태의 JSON 응답을 실제 Python dict로 변환 +# 코드 알고리즘이 문제일 경우 추후 고도화 -> 문자열 첫 {를 찾은 후 문자열 뒤집고 첫 } 찾은 후 크롭하면 됨. +# """ +# if not response_text: +# return None +# +# # 1. ```json ... ``` 또는 ``` ... ``` 코드 블록 제거 +# cleaned = re.sub(r"^```json\s*|\s*```$", "", response_text.strip(), flags=re.DOTALL) +# +# # 2. JSON 문자열 파싱 시도 +# try: +# data = json.loads(cleaned) +# return data +# except json.JSONDecodeError: +# # 코드 블록이 없거나 포맷이 살짝 틀린 경우를 대비 +# try: +# cleaned_alt = re.sub(r"^```|\s*```$", "", response_text.strip(), flags=re.DOTALL) +# data = json.loads(cleaned_alt) +# return data +# except Exception as e: +# print("JSON 파싱 실패:", e) +# print("원본 텍스트:\n", response_text) +# return None + +import json + +def parse_gpt_json_response(response_text: str): + """ + GPT 응답에서 첫 번째 '{'부터 마지막 '}'까지 추출하여 JSON으로 파싱. + GPT가 코드블록이나 안내문을 포함하더라도 안정적으로 동작. + """ + if not response_text: + return None + + # 문자열에서 JSON 본문 추출 + start = response_text.find('{') + end = response_text.rfind('}') + + if start == -1 or end == -1 or start >= end: + print("JSON 형태를 찾을 수 없습니다.") + return None + + json_str = response_text[start:end + 1].strip() + + # JSON 파싱 시도 + try: + return json.loads(json_str) + except json.JSONDecodeError as e: + print("JSON 파싱 실패:", e) + print("추출된 문자열:") + print(json_str) + return None + +def estimate_image_tokens(width: int, height: int) -> int: + """이미지 해상도 기반 토큰 추정""" + tiles_w = (width + 511) // 512 + tiles_h = (height + 511) // 512 + total_tiles = tiles_w * tiles_h + return total_tiles * BASE_IMAGE_TOKENS_512 + +def describe_input(text=None, image_path=None, audio_path=None, model="gpt-4o-mini"): + content = [] + image_tokens_estimated = 0 + w = h = 0 + + if text: + content.append({"type": "text", "text": text}) + + if image_path: + if image_path.startswith("http://") or image_path.startswith("https://"): + # URL 이미지 → 크기 가져오기 + resp = requests.get(image_path, timeout=10) + img = Image.open(BytesIO(resp.content)) + w, h = img.size + image_tokens_estimated = estimate_image_tokens(w, h) + content.append({ + "type": "image_url", + "image_url": {"url": image_path} + }) + else: + # 로컬 이미지 + img_file = Path(image_path) + if not img_file.exists(): + raise FileNotFoundError(f"이미지 파일을 찾을 수 없습니다: {image_path}") + img = Image.open(img_file) + w, h = img.size + image_tokens_estimated = estimate_image_tokens(w, h) + image_data = base64.b64encode(img_file.read_bytes()).decode("utf-8") + content.append({ + "type": "image_url", + "image_url": {"url": f"data:image/jpeg;base64,{image_data}"} + }) + + response = client.chat.completions.create( + model=model, + messages=[{"role": "user", "content": content}] + ) + + result_text = response.choices[0].message.content + cleaned_result = parse_gpt_json_response(result_text) + usage = response.usage + + # 비용 계산 + prompt_cost = usage.prompt_tokens * INPUT_COST_PER_TOKEN + completion_cost = usage.completion_tokens * OUTPUT_COST_PER_TOKEN + image_cost = image_tokens_estimated * INPUT_COST_PER_TOKEN + total_cost_usd = prompt_cost + completion_cost + image_cost + total_cost_krw = total_cost_usd * USD_TO_KRW + + print("\n--- 입력 Prompt ---") + print(text) + print("\n--- GPT 응답 ---") + print(cleaned_result) + print("\n--- 토큰 사용량 ---") + print(f"입력 토큰(prompt_tokens): {usage.prompt_tokens}") + print(f"출력 토큰(completion_tokens): {usage.completion_tokens}") + print(f"총합(total_tokens): {usage.total_tokens}") + if image_path: + print(f"이미지 크기: {w}x{h}px") + print(f"예상 이미지 토큰: {image_tokens_estimated}") + + print("\n--- 예상 비용 ---") + print(f"입력 비용: ${prompt_cost:.6f}") + print(f"출력 비용: ${completion_cost:.6f}") + if image_path: + print(f"이미지 예상 비용: ${image_cost:.6f}") + print(f"총합 예상 비용: ${total_cost_usd:.6f} (≈ {total_cost_krw:,.2f} 원)") + + return result_text, usage, total_cost_usd, total_cost_krw + + + + +if __name__ == "__main__": + test_data_url = "./image_ex/test3.jpeg" + name = "대성부대고기 용인본점" + cathegory = "음식점" + + #describe_input( + # text="이 이미지를 설명해줘.", + # image_path=test_data_url + #) + + describe_input( + text=f"이 이미지 퀄리티를, 해상도 upscale했을 때 {cathegory} - {name}의 광고에 사용 가능한 적합한 이미지인지 설명해줘, 적합하지 않은 데이터 사용시 고소당할 수 있음으로 매우 엄격해야함. 다음 항목에 대한 점수를 상, 중, 하로, json형식으로 제공해줘 : 구도, 노출, 색감, 시각적 노이즈, 내용 적절성. 왜 그렇게 생각하는지에 대한 항목별 설명을 먼저 작성. 답변은" + "{} 안의 json형식으로만 작성", + image_path=test_data_url + ) \ No newline at end of file diff --git a/discriminator.py b/discriminator.py new file mode 100644 index 0000000..4b0cce8 --- /dev/null +++ b/discriminator.py @@ -0,0 +1,31 @@ +from descriptor import describe_input + +def decriminate(json_result, thold_high, thold_low): + """ + 광고 이미지 평가 결과(JSON)를 기반으로 합격/불합격 판정. - GPT API 사용 + 조건: + - thold_high : 최소 '상' 등급 요구치 + - thold_low : 최대 '하' 등급 허용치 + → 두 조건을 모두 만족하면 '합격', 그렇지 않으면 '불합격' + """ + if not isinstance(json_result, dict): + print("입력 데이터가 JSON(dict) 형식이 아닙니다.") + return False + + high_count = 0 + low_count = 0 + + # 설명(description) 키 제외, 주요 평가 항목만 체크 + for key, value in json_result.items(): + if key == "설명": + continue + if isinstance(value, str): + if "상" in value: + high_count += 1 + elif "하" in value: + low_count += 1 + + if high_count >= thold_high and low_count == thold_low: + return True + else: + return False \ No newline at end of file