import cv2 import numpy as np import gradio as gr from datetime import datetime import os import tempfile from PIL import Image, ImageDraw, ImageFont #zh-tw # 定義固定的暫存目錄 TEMP_DIR = os.path.join(tempfile.gettempdir(), "gradio_side2front") os.makedirs(TEMP_DIR, exist_ok=True) def manual_perspective_transform(image, points): if len(points) != 4: return None pts1 = np.array(points, dtype=np.float32) pts2 = np.float32([[0, 0], [300, 0], [0, 400], [300, 400]]) matrix = cv2.getPerspectiveTransform(pts1, pts2) result = cv2.warpPerspective(image, matrix, (300, 400)) return result def format_selected_points(points): labels = ['左上', '右上', '左下', '右下'] formatted_points = [] for i, point in enumerate(points): if i >= len(labels): break # 防止超出標籤範圍 formatted_points.append(f"{labels[i]}[{point[0]}, {point[1]}]") return "、".join(formatted_points) def update_coordinates(original_image, evt: gr.SelectData, points): if original_image is None: # 如果圖片被清除,重置點和狀態訊息 return [], "已重置初始化,請上傳新圖片", "已重置初始化,請上傳新圖片", None if len(points) < 4: # 使用者選擇新的點 points.append([evt.index[0], evt.index[1]]) # 根據已選擇的點生成訊息 formatted_message = f"已選擇的點({len(points)}/4):{format_selected_points(points)}" # 獲取圖片尺寸 height, width = original_image.shape[:2] min_dim = min(width, height) # 定義圓點半徑和字體大小(根據圖片尺寸動態調整) circle_radius = max(10, int(min_dim * 0.005)) # 0.5% 的最小邊長,至少 10 像素 font_size = max(16, int(min_dim * 0.02)) # 2% 的最小邊長,至少 16 像素 # 繪製選取的點在圖片上 annotated = original_image.copy() labels = ['左上', '右上', '左下', '右下'] for i, point in enumerate(points): if i >= len(labels): label = f"Point{i+1}" else: label = labels[i] # 繪製較大的圓點 cv2.circle(annotated, tuple(point), circle_radius, (255, 0, 0), -1) # 紅色圓點RGB # 使用Pillow來繪製文字 pil_image = Image.fromarray(cv2.cvtColor(annotated, cv2.COLOR_BGR2RGB)) # pil_image = Image.fromarray(annotated) draw = ImageDraw.Draw(pil_image) try: # 載入支援中文的字型 font = ImageFont.truetype("GenSekiGothic-B.ttc", font_size) # 確保字型文件路徑正確 except IOError: # 如果字型載入失敗,使用默認字型 font = ImageFont.load_default() for i, point in enumerate(points): if i >= len(labels): label = f"Point{i+1}" else: label = labels[i] # 添加文字標籤,稍微偏移以避免重疊 text_position = (point[0] + 10 + circle_radius, point[1] - 5 - circle_radius) draw.text(text_position, label, font=font, fill=(0, 0, 255)) # 紅色文字BGR # 將Pillow圖像轉回OpenCV圖像 (BGR) annotated = cv2.cvtColor(np.array(pil_image), cv2.COLOR_RGB2BGR) return points, formatted_message, "", annotated def reset_points(original_image): if original_image is None: return [], "已重置所有點", "已重置所有點", None else: return [], "", "已重置所有點", original_image def process_image(image, points, custom_filename, file_format): # 驗證輸入 if image is None: return None, None, None, "請先上傳圖片。" if len(points) != 4: return None, None, None, "請選擇準確的4個點(左上、右上、左下、右下)。" height, width = image.shape[:2] if height < 300 or width < 300: return None, None, None, "圖片尺寸太小。請上傳至少 300x300 像素的圖片。" try: # 處理圖片 result = manual_perspective_transform(image, points) if result is None: return None, None, None, "轉換失敗,請重試。" # 準備檔案名稱 if not custom_filename: custom_filename = f"processedimg_{datetime.now().strftime('%Y%m%d_%H%M%S')}" # 移除任何不安全的字符 custom_filename = "".join(c for c in custom_filename if c.isalnum() or c in ('-', '_')) if not custom_filename: custom_filename = f"processedimg_{datetime.now().strftime('%Y%m%d_%H%M%S')}" # 把結果存到暫存檔 (以便下載) # temp_dir = tempfile.mkdtemp() # out_path = os.path.join(temp_dir, custom_filename + file_format) # cv2.imwrite(out_path, cv2.cvtColor(result, cv2.COLOR_RGB2BGR)) # 把結果存到固定的暫存檔,覆蓋舊檔案 out_path = os.path.join(TEMP_DIR, custom_filename + file_format) cv2.imwrite(out_path, cv2.cvtColor(result, cv2.COLOR_RGB2BGR)) return result, out_path, out_path, "處理成功!" except Exception as e: return None, None, None, f"處理過程發生錯誤:{str(e)}" # 修改圖片變更事件處理函數 def handle_image_change(img): if img is not None: return img, [], "", "已上傳新圖片,請標示四個角落點", img return None, [], "已清除圖片,請上傳新圖", "已清除圖片,請上傳新圖", None # Gradio Interface Setup with gr.Blocks(theme=gr.themes.Default(primary_hue=gr.themes.colors.yellow, secondary_hue=gr.themes.colors.red)) as interface: # 標題和描述 gr.HTML("""