|
|
"""画像処理ユーティリティ""" |
|
|
|
|
|
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エンコード""" |
|
|
|
|
|
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] |
|
|
|