{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# %% [markdown]\n",
    "\"\"\"\n",
    "# 전역 변수 및 하이퍼파라미터 설정\n",
    "\n",
    "- **VIDEO_PATH**: 원본 입력 영상 경로  \n",
    "- **OUTPUT_DIR_BASE**: 출력 결과를 저장할 기본 디렉토리  \n",
    "- **RESIZE_WIDTH, RESIZE_HEIGHT**: 영상 리사이징 해상도 (예: 1920x1080, 1280x720 등)  \n",
    "  - 예시 추천 해상도: 1920x1080, 1280x720, 640x480 등  \n",
    "- **TARGET_FPS**: 변경할 프레임 레이트 (예: 30 FPS)  \n",
    "- **CROP_TOP, CROP_BOTTOM, CROP_LEFT, CROP_RIGHT**: 영상 각 가장자리에서 제거할 픽셀 수  \n",
    "- **ROI_USE_GUI**: ROI 영역을 GUI로 지정할 것인지(True) 또는 코드 내 수동 지정(False)할 것인지  \n",
    "- **MANUAL_ROI**: GUI 사용이 off일 때, (x, y, width, height) 형식으로 직접 지정한 ROI 영역  \n",
    "- **BLUR_KERNEL_SIZE**: ROI 영역에 적용할 가우시안 블러의 커널 사이즈 (블러 강도 조절)\n",
    "\"\"\"\n",
    "# %% [code]\n",
    "import os\n",
    "import cv2\n",
    "import numpy as np\n",
    "import matplotlib.pyplot as plt\n",
    "from datetime import datetime\n",
    "\n",
    "# (!!!) 자주 바꿀 수 있는 글로벌 파라미터\n",
    "VIDEO_PATH = \"/ltb/media/ltb/90887173887158A4/Users/ltb/회사용/4090_옮길거/val_sample/it1_현대_테스트할영상_원본/스팀_작업자_헬멧_중장비_01.avi\"  # 입력 영상 경로\n",
    "OUTPUT_DIR_BASE = \"/ltb/media/ltb/90887173887158A4/Users/ltb/회사용/4090_옮길거/val_sample/it1_현대_테스트할영상_원본/exp_output\"  # 출력 폴더 기본 경로\n",
    "\n",
    "# 영상 리사이징 해상도 (예시: 1920x1080, 1280x720, 640x480 등)\n",
    "RESIZE_WIDTH = 1920   \n",
    "RESIZE_HEIGHT = 1080  \n",
    "TARGET_FPS = 30  # 원하는 FPS (필요시, 영상 재샘플링)\n",
    "\n",
    "# 영상 가장자리 크롭 픽셀 값 (상, 하, 좌, 우)\n",
    "CROP_TOP = 0       # 상단 10픽셀 제거\n",
    "CROP_BOTTOM = 0    # 하단 10픽셀 제거\n",
    "CROP_LEFT = 0      # 좌측 10픽셀 제거\n",
    "CROP_RIGHT = 0     # 우측 10픽셀 제거\n",
    "\n",
    "# ROI(Region of Interest) 블러 처리 관련 설정\n",
    "ROI_USE_GUI = False  # True: GUI로 ROI 지정, False: 코드 내 직접 좌표 지정 (headless 환경 대비)\n",
    "MANUAL_ROI = (960, 100, 1200, 300)  # (x, y, width, height) - GUI 사용 안할 경우 적용\n",
    "BLUR_KERNEL_SIZE = (15, 15)  # 가우시안 블러 커널 사이즈 (블러 강도 조절)\n",
    "\n",
    "# 중간 디버깅용 프레임 지정 (예: 첫 프레임 또는 특정 프레임 번호)\n",
    "DEBUG_FRAME_IDX = 2\n",
    "\n",
    "# 출력 파일명에 사용될 시간정보 (폴더 및 파일 구분에 도움)\n",
    "timestamp = datetime.now().strftime(\"%Y%m%d_%H%M%S\")\n",
    "# 출력 디렉토리 생성 (기존 파일 덮어쓰지 않고, 번호/타임스탬프 부여)\n",
    "output_dir = os.path.join(OUTPUT_DIR_BASE, f\"exp_{timestamp}\")\n",
    "os.makedirs(output_dir, exist_ok=True)\n",
    "\n",
    "print(\"✅ 전역 변수 설정 완료\")\n",
    "print(f\"INPUT VIDEO: {VIDEO_PATH}\")\n",
    "print(f\"OUTPUT DIRECTORY: {output_dir}\")\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# %% [markdown]\n",
    "\"\"\"\n",
    "# 영상 정보 읽기 및 첫 프레임 출력\n",
    "\n",
    "- cv2.VideoCapture를 이용하여 영상 정보를 읽어옵니다.\n",
    "- 해상도, FPS, 총 프레임 수, 영상 길이(초) 등을 출력합니다.\n",
    "- DEBUG_FRAME_IDX에 지정된 프레임(기본: 첫 프레임)을 추출하여 matplotlib로 시각화하고 파일로 저장합니다.\n",
    "\"\"\"\n",
    "# %% [code]\n",
    "# 영상 정보 읽기\n",
    "cap = cv2.VideoCapture(VIDEO_PATH)\n",
    "if not cap.isOpened():\n",
    "    raise IOError(f\"영상 열기 실패: {VIDEO_PATH}\")\n",
    "\n",
    "frame_width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))\n",
    "frame_height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))\n",
    "fps = cap.get(cv2.CAP_PROP_FPS)\n",
    "frame_count = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))\n",
    "duration = frame_count / fps if fps > 0 else 0\n",
    "\n",
    "print(\"📌 영상 정보:\")\n",
    "print(f\" - 해상도: {frame_width} x {frame_height}\")\n",
    "print(f\" - FPS: {fps}\")\n",
    "print(f\" - 총 프레임 수: {frame_count}\")\n",
    "print(f\" - 영상 길이: {duration:.2f} 초\")\n",
    "\n",
    "# 디버깅: DEBUG_FRAME_IDX 프레임 추출\n",
    "cap.set(cv2.CAP_PROP_POS_FRAMES, DEBUG_FRAME_IDX)\n",
    "ret, debug_frame = cap.read()\n",
    "if not ret:\n",
    "    raise IOError(f\"DEBUG_FRAME_IDX({DEBUG_FRAME_IDX}) 프레임 읽기 실패\")\n",
    "# BGR -> RGB 변환 (matplotlib 시각화를 위해)\n",
    "debug_frame_rgb = cv2.cvtColor(debug_frame, cv2.COLOR_BGR2RGB)\n",
    "\n",
    "# matplotlib으로 첫 프레임 시각화 및 파일 저장\n",
    "plt.figure(figsize=(8, 6))\n",
    "plt.imshow(debug_frame_rgb)\n",
    "plt.title(\"원본 디버그 프레임\")\n",
    "plt.axis(\"off\")\n",
    "plt.show()\n",
    "\n",
    "debug_frame_path = os.path.join(output_dir, \"frame_debug_original.jpg\")\n",
    "cv2.imwrite(debug_frame_path, debug_frame)\n",
    "print(f\"✅ 디버그 원본 프레임 저장: {debug_frame_path}\")\n",
    "\n",
    "cap.release()\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# %% [markdown]\n",
    "\"\"\"\n",
    "# 영상 리사이징 및 FPS 조절 함수 정의\n",
    "\n",
    "- resize_frame: 입력 프레임을 전역 변수로 지정한 RESIZE_WIDTH, RESIZE_HEIGHT로 리사이징합니다.\n",
    "- (추가로 FPS 조절이 필요하면, 프레임 선택 등 별도 로직 추가 가능)\n",
    "\"\"\"\n",
    "# %% [code]\n",
    "def resize_frame(frame, width=RESIZE_WIDTH, height=RESIZE_HEIGHT):\n",
    "    \"\"\"\n",
    "    입력 프레임을 지정된 해상도로 리사이징하는 함수.\n",
    "    매개변수:\n",
    "      - frame: 원본 영상 프레임 (numpy 배열)\n",
    "      - width, height: 목표 해상도\n",
    "    반환:\n",
    "      - 리사이징된 프레임\n",
    "    \"\"\"\n",
    "    resized = cv2.resize(frame, (width, height))\n",
    "    return resized\n",
    "\n",
    "# 예시: 디버깅을 위해 첫 프레임 리사이징 후 시각화\n",
    "cap = cv2.VideoCapture(VIDEO_PATH)\n",
    "cap.set(cv2.CAP_PROP_POS_FRAMES, DEBUG_FRAME_IDX)\n",
    "ret, frame_example = cap.read()\n",
    "if not ret:\n",
    "    raise IOError(\"프레임 읽기 실패\")\n",
    "resized_example = resize_frame(frame_example)\n",
    "resized_example_rgb = cv2.cvtColor(resized_example, cv2.COLOR_BGR2RGB)\n",
    "\n",
    "plt.figure(figsize=(8, 6))\n",
    "plt.imshow(resized_example_rgb)\n",
    "plt.title(\"리사이징된 디버그 프레임\")\n",
    "plt.axis(\"off\")\n",
    "plt.show()\n",
    "\n",
    "# 저장 예시\n",
    "resized_example_path = os.path.join(output_dir, \"frame_debug_resized.jpg\")\n",
    "cv2.imwrite(resized_example_path, resized_example)\n",
    "print(f\"✅ 리사이징 디버그 프레임 저장: {resized_example_path}\")\n",
    "cap.release()\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# %% [markdown]\n",
    "\"\"\"\n",
    "# 영상 크롭(상하좌우 편집) 함수 및 디버깅\n",
    "\n",
    "- crop_frame: 입력 프레임에서 상하좌우 픽셀만큼 크롭하는 함수\n",
    "- DEBUG: 지정된 DEBUG_FRAME_IDX 프레임에 대해 원본과 크롭된 결과를 나란히 비교하여 시각화 및 저장\n",
    "\"\"\"\n",
    "# %% [code]\n",
    "def crop_frame(frame, top=CROP_TOP, bottom=CROP_BOTTOM, left=CROP_LEFT, right=CROP_RIGHT):\n",
    "    \"\"\"\n",
    "    영상 프레임에서 상하좌우 가장자리 픽셀을 지정된 만큼 크롭하는 함수.\n",
    "    매개변수:\n",
    "      - frame: 입력 영상 프레임\n",
    "      - top, bottom, left, right: 각 방향에서 제거할 픽셀 수\n",
    "    반환:\n",
    "      - 크롭된 프레임\n",
    "    \"\"\"\n",
    "    h, w = frame.shape[:2]\n",
    "    cropped = frame[top:h-bottom, left:w-right]\n",
    "    return cropped\n",
    "\n",
    "# 디버깅: DEBUG_FRAME_IDX 프레임을 대상으로 크롭 전후 비교\n",
    "cap = cv2.VideoCapture(VIDEO_PATH)\n",
    "cap.set(cv2.CAP_PROP_POS_FRAMES, DEBUG_FRAME_IDX)\n",
    "ret, frame_for_crop = cap.read()\n",
    "if not ret:\n",
    "    raise IOError(\"DEBUG_FRAME_IDX 프레임 읽기 실패\")\n",
    "# 리사이징 후 크롭 적용 (전체 파이프라인 적용 시 리사이징 후 크롭)\n",
    "frame_resized = resize_frame(frame_for_crop)\n",
    "frame_cropped = crop_frame(frame_resized)\n",
    "\n",
    "# 시각화를 위해 BGR -> RGB 변환\n",
    "frame_resized_rgb = cv2.cvtColor(frame_resized, cv2.COLOR_BGR2RGB)\n",
    "frame_cropped_rgb = cv2.cvtColor(frame_cropped, cv2.COLOR_BGR2RGB)\n",
    "\n",
    "# 원본과 크롭된 결과를 나란히 표시\n",
    "fig, axes = plt.subplots(1, 2, figsize=(12, 6))\n",
    "axes[0].imshow(frame_resized_rgb)\n",
    "axes[0].set_title(\"리사이징된 원본 프레임\")\n",
    "axes[0].axis(\"off\")\n",
    "axes[1].imshow(frame_cropped_rgb)\n",
    "axes[1].set_title(\"상하좌우 크롭 적용 프레임\")\n",
    "axes[1].axis(\"off\")\n",
    "plt.show()\n",
    "\n",
    "# 디버깅 이미지 저장\n",
    "resized_debug_path = os.path.join(output_dir, \"frame_debug_resized_for_crop.jpg\")\n",
    "cropped_debug_path = os.path.join(output_dir, \"frame_debug_cropped.jpg\")\n",
    "cv2.imwrite(resized_debug_path, frame_resized)\n",
    "cv2.imwrite(cropped_debug_path, frame_cropped)\n",
    "print(f\"✅ 리사이징 프레임 저장: {resized_debug_path}\")\n",
    "print(f\"✅ 크롭된 프레임 저장: {cropped_debug_path}\")\n",
    "cap.release()\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# %% [markdown]\n",
    "\"\"\"\n",
    "# ROI 영역 블러 처리 함수 및 ROI 선택\n",
    "\n",
    "- select_roi: ROI를 선택하는 함수 (GUI 방식 또는 수동 지정)\n",
    "- apply_roi_blur: 지정한 ROI 영역에 가우시안 블러를 적용하는 함수\n",
    "- GUI 방식 선택 시, cv2.selectROI를 사용 (주의: headless 환경에서는 비활성화)\n",
    "\"\"\"\n",
    "# %% [code]\n",
    "def select_roi(frame):\n",
    "    \"\"\"\n",
    "    ROI 영역을 선택하는 함수.\n",
    "    ROI_USE_GUI가 True이면 cv2.selectROI를 이용하여 마우스 클릭으로 지정,\n",
    "    False이면 전역 변수 MANUAL_ROI (x, y, w, h)를 사용.\n",
    "    \"\"\"\n",
    "    if ROI_USE_GUI:\n",
    "        # GUI 방식 (윈도우가 뜨지만 headless 서버에서는 작동하지 않을 수 있음)\n",
    "        print(\"👉 ROI 선택: 마우스로 영역 지정 후 Enter 키를 누르세요.\")\n",
    "        roi = cv2.selectROI(\"ROI Selector\", frame, fromCenter=False, showCrosshair=True)\n",
    "        cv2.destroyWindow(\"ROI Selector\")\n",
    "        # roi는 (x, y, w, h) 형식으로 반환됨\n",
    "        return roi\n",
    "    else:\n",
    "        print(\"👉 수동 ROI 지정 사용:\", MANUAL_ROI)\n",
    "        return MANUAL_ROI\n",
    "\n",
    "def apply_roi_blur(frame, roi, kernel_size=BLUR_KERNEL_SIZE):\n",
    "    \"\"\"\n",
    "    지정한 ROI 영역에 가우시안 블러를 적용하는 함수.\n",
    "    매개변수:\n",
    "      - frame: 입력 영상 프레임\n",
    "      - roi: (x, y, w, h) 형식의 ROI 영역\n",
    "      - kernel_size: 가우시안 블러 커널 사이즈\n",
    "    반환:\n",
    "      - 블러 적용된 프레임\n",
    "    \"\"\"\n",
    "    x, y, w, h = roi\n",
    "    # ROI 영역 복사 후 블러 처리\n",
    "    roi_region = frame[y:y+h, x:x+w]\n",
    "    roi_blurred = cv2.GaussianBlur(roi_region, kernel_size, 0)\n",
    "    # 블러 처리된 영역을 원본 프레임에 다시 삽입\n",
    "    frame_blurred = frame.copy()\n",
    "    frame_blurred[y:y+h, x:x+w] = roi_blurred\n",
    "    return frame_blurred\n",
    "\n",
    "# 디버깅: DEBUG_FRAME_IDX 프레임에서 ROI 선택 및 블러 적용 결과 비교\n",
    "cap = cv2.VideoCapture(VIDEO_PATH)\n",
    "cap.set(cv2.CAP_PROP_POS_FRAMES, DEBUG_FRAME_IDX)\n",
    "ret, frame_for_roi = cap.read()\n",
    "if not ret:\n",
    "    raise IOError(\"DEBUG_FRAME_IDX 프레임 읽기 실패 (ROI)\")\n",
    "# 리사이징, 크롭 적용 후 ROI 지정 (파이프라인에 맞게 적용)\n",
    "frame_resized_for_roi = resize_frame(frame_for_roi)\n",
    "frame_cropped_for_roi = crop_frame(frame_resized_for_roi)\n",
    "\n",
    "# ROI 영역 선택 (GUI 또는 수동)\n",
    "roi_selected = select_roi(frame_cropped_for_roi)\n",
    "\n",
    "# 원본(크롭된 프레임)과 ROI 블러 처리 결과 비교\n",
    "frame_roi_blurred = apply_roi_blur(frame_cropped_for_roi, roi_selected)\n",
    "\n",
    "# 시각화 (BGR -> RGB 변환)\n",
    "frame_cropped_rgb = cv2.cvtColor(frame_cropped_for_roi, cv2.COLOR_BGR2RGB)\n",
    "frame_roi_blurred_rgb = cv2.cvtColor(frame_roi_blurred, cv2.COLOR_BGR2RGB)\n",
    "\n",
    "fig, axes = plt.subplots(1, 2, figsize=(12, 6))\n",
    "axes[0].imshow(frame_cropped_rgb)\n",
    "axes[0].set_title(\"크롭된 원본 (ROI 적용 전)\")\n",
    "axes[0].axis(\"off\")\n",
    "axes[1].imshow(frame_roi_blurred_rgb)\n",
    "axes[1].set_title(\"ROI 블러 처리 결과\")\n",
    "axes[1].axis(\"off\")\n",
    "plt.show()\n",
    "\n",
    "# 디버깅 이미지 저장\n",
    "roi_before_path = os.path.join(output_dir, \"frame_debug_roi_before.jpg\")\n",
    "roi_after_path = os.path.join(output_dir, \"frame_debug_roi_after.jpg\")\n",
    "cv2.imwrite(roi_before_path, frame_cropped_for_roi)\n",
    "cv2.imwrite(roi_after_path, frame_roi_blurred)\n",
    "print(f\"✅ ROI 적용 전 이미지 저장: {roi_before_path}\")\n",
    "print(f\"✅ ROI 블러 처리 이미지 저장: {roi_after_path}\")\n",
    "cap.release()\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# %% [markdown]\n",
    "\"\"\"\n",
    "# 전체 영상 편집 파이프라인 및 결과 저장\n",
    "\n",
    "- 입력 영상의 모든 프레임에 대해, 순차적으로:\n",
    "  1. 리사이징\n",
    "  2. 상하좌우 크롭\n",
    "  3. ROI 블러 처리\n",
    "- 처리된 영상은 cv2.VideoWriter를 사용하여 새로운 파일로 저장합니다.\n",
    "- 파일명 및 폴더명에 시간/실험 번호를 포함하여 덮어쓰지 않고 관리합니다.\n",
    "- 처리 중간에 프레임 번호 등을 출력하여 진행 상황을 디버깅할 수 있도록 합니다.\n",
    "\"\"\"\n",
    "# %% [code]\n",
    "def process_video(input_path, output_path):\n",
    "    \"\"\"\n",
    "    전체 영상 편집 파이프라인:\n",
    "      - 리사이징, 크롭, ROI 블러 적용\n",
    "      - 처리된 영상을 output_path에 저장\n",
    "    \"\"\"\n",
    "    cap = cv2.VideoCapture(input_path)\n",
    "    if not cap.isOpened():\n",
    "        raise IOError(f\"영상 열기 실패: {input_path}\")\n",
    "    \n",
    "    # 입력 영상 정보\n",
    "    original_fps = cap.get(cv2.CAP_PROP_FPS)\n",
    "    print(f\"👉 입력 FPS: {original_fps}\")\n",
    "    \n",
    "    # 첫 프레임 처리하여 출력 프레임 크기 결정 (리사이징 -> 크롭)\n",
    "    ret, frame_sample = cap.read()\n",
    "    if not ret:\n",
    "        raise IOError(\"첫 프레임 읽기 실패\")\n",
    "    frame_sample = resize_frame(frame_sample)\n",
    "    frame_sample = crop_frame(frame_sample)\n",
    "    # (ROI 블러 처리은 영역 내 픽셀만 변경되므로 출력 크기는 동일)\n",
    "    output_height, output_width = frame_sample.shape[:2]\n",
    "    \n",
    "    # 코덱 설정 (XVID 등 사용, 환경에 따라 변경 가능)\n",
    "    fourcc = cv2.VideoWriter_fourcc(*\"XVID\")\n",
    "    # output video path\n",
    "    out_video_path = output_path  # 예: /ltb/.../exp_20230401_123456/output_video.avi\n",
    "    out = cv2.VideoWriter(out_video_path, fourcc, TARGET_FPS, (output_width, output_height))\n",
    "    \n",
    "    # 전체 프레임 처리\n",
    "    frame_idx = 0\n",
    "    total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))\n",
    "    print(\"영상 편집 시작...\")\n",
    "    while True:\n",
    "        ret, frame = cap.read()\n",
    "        if not ret:\n",
    "            break\n",
    "        # 리사이징, 크롭 적용\n",
    "        frame = resize_frame(frame)\n",
    "        frame = crop_frame(frame)\n",
    "        # ROI 블러 적용\n",
    "        frame = apply_roi_blur(frame, roi_selected)\n",
    "        # 프레임 쓰기\n",
    "        out.write(frame)\n",
    "        frame_idx += 1\n",
    "        if frame_idx % 50 == 0:\n",
    "            print(f\"  처리 중... 프레임 {frame_idx} / {total_frames}\")\n",
    "    \n",
    "    cap.release()\n",
    "    out.release()\n",
    "    print(\"✅ 영상 편집 완료 및 저장됨:\", out_video_path)\n",
    "    return out_video_path\n",
    "\n",
    "# 출력 영상 파일명 지정 (exp 폴더 내에 저장, 덮어쓰지 않도록 타임스탬프 사용)\n",
    "output_video_path = os.path.join(output_dir, f\"edited_video_{timestamp}.avi\")\n",
    "result_video = process_video(VIDEO_PATH, output_video_path)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.8.20"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
