"""画像処理ユーティリティ""" import base64 import io from typing import Tuple import cv2 import numpy as np from PIL import Image def resize_frame( frame: np.ndarray, width: int = 640, height: int = 480 ) -> np.ndarray: """フレームをリサイズ""" return cv2.resize(frame, (width, height), interpolation=cv2.INTER_AREA) def frame_to_base64(frame: np.ndarray, quality: int = 85) -> str: """フレームをBase64エンコード""" # BGR -> RGB if len(frame.shape) == 3 and frame.shape[2] == 3: frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) else: frame_rgb = frame img = Image.fromarray(frame_rgb) buffer = io.BytesIO() img.save(buffer, format="JPEG", quality=quality) return base64.b64encode(buffer.getvalue()).decode("utf-8") def frame_to_pil(frame: np.ndarray) -> Image.Image: """NumPy配列をPIL Imageに変換""" if len(frame.shape) == 3 and frame.shape[2] == 3: frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) else: frame_rgb = frame return Image.fromarray(frame_rgb) def pil_to_frame(img: Image.Image) -> np.ndarray: """PIL ImageをNumPy配列に変換""" frame = np.array(img) if len(frame.shape) == 3 and frame.shape[2] == 3: frame = cv2.cvtColor(frame, cv2.COLOR_RGB2BGR) return frame def rotate_frame(frame: np.ndarray, angle: int) -> np.ndarray: """フレームを回転(0, 90, 180, 270度)""" if angle == 90: return cv2.rotate(frame, cv2.ROTATE_90_CLOCKWISE) elif angle == 180: return cv2.rotate(frame, cv2.ROTATE_180) elif angle == 270: return cv2.rotate(frame, cv2.ROTATE_90_COUNTERCLOCKWISE) return frame def get_frame_dimensions(frame: np.ndarray) -> Tuple[int, int]: """フレームの寸法を取得 (width, height)""" return frame.shape[1], frame.shape[0]