Spaces:
Sleeping
Sleeping
import json | |
import numpy as np | |
from PIL import Image | |
import subprocess | |
import sys | |
import logging | |
import gradio as gr | |
from typing import Tuple, Dict, Optional | |
import tempfile | |
import os | |
def install(package): | |
subprocess.check_call([sys.executable, "-m", "pip", "install", package]) | |
# 依存関係のインストール | |
install("opencv-python-headless") | |
install("moondream") | |
install("openai") | |
import cv2 | |
import moondream as md | |
from openai import OpenAI | |
class PrivacyProtector: | |
def __init__(self, moondream_api_key, deepseek_api_key): | |
self.moon_model = md.vl(api_key=moondream_api_key) | |
self.deepseek_client = OpenAI( | |
api_key=deepseek_api_key, | |
base_url="https://api.deepseek.com" | |
) | |
def analyze_risk(self, image_path): | |
"""画像のリスク分析を行う""" | |
try: | |
pil_image = Image.open(image_path) | |
cv_image = cv2.imread(image_path) | |
if cv_image is None: | |
raise ValueError("画像の読み込みに失敗しました") | |
encoded_image = self.moon_model.encode_image(pil_image) | |
caption = self.moon_model.caption(encoded_image)["caption"] | |
analysis_prompt = f""" | |
以下の画像説明を基に個人情報漏洩リスクを分析し、厳密にJSON形式で返答してください: | |
{{ | |
"risk_level": "high|medium|low", | |
"risk_reason": "リスクの具体的理由", | |
"objects_to_remove": ["消去すべきオブジェクトリスト"] | |
}} | |
画像説明: {caption} | |
""" | |
response = self.deepseek_client.chat.completions.create( | |
model="deepseek-chat", | |
messages=[ | |
{"role": "system", "content": "あなたは優秀なセキュリティ分析AIです。"}, | |
{"role": "user", "content": analysis_prompt} | |
], | |
temperature=0.3, | |
response_format={"type": "json_object"} | |
) | |
result = json.loads(response.choices[0].message.content) | |
return pil_image, cv_image, result | |
except Exception as e: | |
logging.error(f"リスク分析エラー: {str(e)}") | |
raise | |
def remove_objects(self, pil_image, cv_image, objects_to_remove): | |
"""オブジェクト検出と消去処理""" | |
try: | |
mask = np.zeros(cv_image.shape[:2], dtype=np.uint8) | |
h, w = cv_image.shape[:2] | |
for obj_name in objects_to_remove: | |
detection = self.moon_model.detect(pil_image, obj_name) | |
for obj in detection["objects"]: | |
x_min = int(obj['x_min'] * w) | |
y_min = int(obj['y_min'] * h) | |
x_max = int(obj['x_max'] * w) | |
y_max = int(obj['y_max'] * h) | |
cv2.rectangle(mask, (x_min, y_min), (x_max, y_max), 255, -1) | |
inpainted = cv2.inpaint(cv_image, mask, 3, cv2.INPAINT_TELEA) | |
return inpainted | |
except Exception as e: | |
logging.error(f"オブジェクト削除エラー: {str(e)}") | |
raise | |
def process_image(self, image_path): | |
"""画像処理フロー全体""" | |
try: | |
pil_img, cv_img, result = self.analyze_risk(image_path) | |
if result['risk_level'] != 'low': | |
cleaned = self.remove_objects(pil_img, cv_img, result['objects_to_remove']) | |
return cleaned, result | |
return cv_img, result | |
except Exception as e: | |
logging.error(f"画像処理エラー: {str(e)}") | |
raise | |
def gradio_process(input_image): | |
try: | |
# 一時ファイルの処理 | |
with tempfile.NamedTemporaryFile(suffix=".jpg", delete=False) as tmp_file: | |
input_path = tmp_file.name | |
cv2.imwrite(input_path, cv2.cvtColor(input_image, cv2.COLOR_RGB2BGR)) | |
# 環境変数の安全な取得 | |
protector = PrivacyProtector( | |
moondream_api_key=os.environ.get('MOONDREAM'), | |
deepseek_api_key=os.environ.get('DEEPSEEK') | |
) | |
# 画像処理実行 | |
processed_image, result = protector.process_image(input_path) | |
# 結果の整形 | |
output_image = cv2.cvtColor(processed_image, cv2.COLOR_BGR2RGB) | |
info_html = f""" | |
<div style="padding:20px; background:#f0f0f0; border-radius:10px;"> | |
<h3>分析結果</h3> | |
<p><strong>生成キャプション:</strong> {result.get('caption', '')}</p> | |
<p><strong>リスクレベル:</strong> <span style="color:{'red' if result['risk_level'] == 'high' else 'orange' if result['risk_level'] == 'medium' else 'green'}">{result['risk_level'].upper()}</span></p> | |
<p><strong>理由:</strong> {result['risk_reason']}</p> | |
<p><strong>消去対象:</strong> {', '.join(result['objects_to_remove'])}</p> | |
</div> | |
""" | |
return input_image, output_image, info_html | |
except Exception as e: | |
error_msg = f"処理中にエラーが発生しました: {str(e)}" | |
return input_image, None, f'<div style="color:red;">{error_msg}</div>' | |
finally: | |
# 一時ファイルのクリーンアップ | |
if 'input_path' in locals() and os.path.exists(input_path): | |
os.remove(input_path) | |
with gr.Blocks(theme=gr.themes.Soft()) as demo: | |
gr.Markdown("# 🛡️ プライバシー保護画像処理ツール") | |
with gr.Row(): | |
with gr.Column(scale=2): | |
input_img = gr.Image(label="入力画像", type="numpy") | |
submit_btn = gr.Button("画像を分析・処理", variant="primary") | |
with gr.Column(scale=2): | |
output_img = gr.Image(label="処理後の画像") | |
info_output = gr.HTML(label="分析結果") | |
with gr.Accordion("処理の詳細", open=False): | |
gr.Markdown(""" | |
**処理フロー:** | |
1. 画像キャプション生成 (Moondream) | |
2. リスク分析 (DeepSeek) | |
3. オブジェクト検出・消去 (Moondream + OpenCV) | |
""") | |
submit_btn.click( | |
fn=gradio_process, | |
inputs=input_img, | |
outputs=[input_img, output_img, info_output] | |
) | |
if __name__ == "__main__": | |
demo.launch() |