##############################################################
# eCamera
# By : GSSEO Date : 2024.10.29
##############################################################
import cv2
from kivy.app import App
from kivy.clock import Clock
from kivy.graphics.texture import Texture
from kivy.uix.image import Image
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.scrollview import ScrollView
from kivy.uix.gridlayout import GridLayout
from kivy.uix.button import Button
from kivy.uix.popup import Popup
from kivy.core.window import Window
import time
import os
from kivy.input.motionevent import MotionEvent
import numpy as np
class CameraApp(App):
def build(self):
# 메인 레이아웃 설정 (왼쪽: 이미지 리스트, 오른쪽: 카메라 화면)
self.layout = BoxLayout(orientation='horizontal')
print("[INFO] Main layout initialized.")
# 이미지 리스트 레이아웃 설정
# 이미지 목록은 왼쪽에 표시되고 스크롤뷰로 감싸여 있어 스크롤이 가능함
self.image_list_layout = GridLayout(cols=1, spacing=5, size_hint_y=None)
self.image_list_layout.bind(minimum_height=self.image_list_layout.setter('height'))
self.scroll_view = ScrollView(size_hint=(0.3, 1))
self.scroll_view.add_widget(self.image_list_layout)
self.layout.add_widget(self.scroll_view)
print("[INFO] Image list layout initialized.")
# 카메라 화면 레이아웃 설정
# 오른쪽에 카메라 화면을 표시할 위젯을 배치함
self.camera_layout = BoxLayout(orientation='vertical')
self.img_widget = Image()
self.camera_layout.add_widget(self.img_widget)
print("[INFO] Camera display initialized.")
# 저장 버튼 추가
# 이미지를 저장할 수 있는 버튼을 추가하고 클릭 시 save_image 메서드를 호출
save_btn = Button(text='SAVE IMAGE', size_hint_y=None, height=40)
save_btn.bind(on_release=self.save_image)
self.camera_layout.add_widget(save_btn)
print("[INFO] Save image button added.")
# 경계선 표시 버튼 추가
# 경계선을 표시하거나 숨길 수 있는 버튼을 추가하고 클릭 시 toggle_boundary 메서드를 호출
boundary_btn = Button(text='SHOW BOUNDARIES', size_hint_y=None, height=40)
boundary_btn.bind(on_release=self.toggle_boundary)
self.camera_layout.add_widget(boundary_btn)
print("[INFO] Boundary toggle button added.")
# 경계선 지우기 버튼 추가
# 경계선을 지우는 버튼을 추가하고 클릭 시 clear_boundary 메서드를 호출
clear_btn = Button(text='CLEAR BOUNDARIES', size_hint_y=None, height=40)
clear_btn.bind(on_release=self.clear_boundary)
self.camera_layout.add_widget(clear_btn)
print("[INFO] Clear boundary button added.")
# 종료 버튼 추가
# 프로그램을 종료할 수 있는 버튼을 추가하고 클릭 시 stop_app 메서드를 호출
exit_btn = Button(text='EXIT', size_hint_y=None, height=40)
exit_btn.bind(on_release=self.stop_app)
self.camera_layout.add_widget(exit_btn)
print("[INFO] Exit button added.")
# 카메라 화면과 버튼을 메인 레이아웃에 추가
self.layout.add_widget(self.camera_layout)
print("[INFO] Camera layout added to main layout.")
# OpenCV를 사용하여 카메라 초기화
self.capture = cv2.VideoCapture(0)
if not self.capture.isOpened():
raise Exception("카메라를 열 수 없습니다. 연결 상태를 확인해주세요.")
print("[INFO] Camera initialized.")
# 경계선 표시 여부 플래그
self.show_boundary = False
# 주기적으로 화면을 업데이트하기 위해 Clock 사용
Clock.schedule_interval(self.update, 1.0 / 30.0) # 30fps로 화면 갱신
print("[INFO] Clock scheduled for frame update.")
# 저장된 이미지 목록 업데이트
self.update_image_list()
print("[INFO] Image list updated.")
return self.layout
def update(self, dt):
# 카메라 프레임 읽기
if not self.capture.isOpened():
print("[ERROR] Capture device is not opened.")
return
ret, frame = self.capture.read()
if not ret:
print("[ERROR] Failed to capture frame.")
return
print("[DEBUG] Frame captured successfully.")
# 프레임을 수직으로 뒤집기 (카메라 상하 반전)
frame = cv2.flip(frame, 0)
# 경계선 표시가 활성화된 경우 경계선을 그림
if self.show_boundary:
print("[DEBUG] Applying edge detection.")
# 그레이스케일로 변환 후 Canny 알고리즘을 사용해 경계선을 검출
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
edges = cv2.Canny(gray, 100, 200)
frame[edges != 0] = [0, 0, 255] # 경계선에 빨간색 적용
# BGR에서 RGB로 변환 (OpenCV는 BGR, Kivy는 RGB 사용)
frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
# numpy array를 Kivy Texture로 변환
# 현재 프레임을 저장하고 Kivy에서 사용할 수 있는 Texture로 변환함
self.current_frame = frame
buf = frame.tobytes()
texture = Texture.create(size=(frame.shape[1], frame.shape[0]), colorfmt='rgb')
texture.blit_buffer(buf, colorfmt='rgb', bufferfmt='ubyte')
print("[DEBUG] Frame converted to texture.")
# 화면에 이미지 설정
self.img_widget.texture = texture
def save_image(self, instance):
# 저장 버튼 클릭 시 이미지 저장
if not hasattr(self, 'current_frame'):
print("[WARNING] No frame available to save.")
return
# 파일 이름을 현재 날짜와 시간으로 설정하여 고유하게 만듦
filename = f"capture_{time.strftime('%Y%m%d_%H%M%S')}.png"
# 저장 시 프레임을 다시 뒤집기 (원래 방향으로 저장)
frame_bgr = cv2.cvtColor(self.current_frame, cv2.COLOR_RGB2BGR)
frame_bgr = cv2.flip(frame_bgr, 0)
success = cv2.imwrite(filename, frame_bgr)
if success:
print(f"[INFO] Image saved: {filename}")
self.update_image_list() # 이미지 리스트 업데이트
else:
print("[ERROR] Failed to save image.")
def toggle_boundary(self, instance):
# 경계선 표시 여부 토글
self.show_boundary = not self.show_boundary
print(f"[INFO] Boundary display toggled: {'Enabled' if self.show_boundary else 'Disabled'}")
def clear_boundary(self, instance):
# 경계선 표시 비활성화
self.show_boundary = False
print("[INFO] Boundaries cleared.")
def update_image_list(self):
# 이미지 리스트를 초기화하고 업데이트
# 현재 디렉토리에서 'capture_'로 시작하는 모든 PNG 파일을 찾아 리스트에 추가
self.image_list_layout.clear_widgets()
for filename in sorted(os.listdir('.')):
if filename.startswith('capture_') and filename.endswith('.png'):
btn = Button(text=filename, size_hint_y=None, height=40)
btn.bind(on_release=self.show_image_popup)
self.image_list_layout.add_widget(btn)
print("[INFO] Image list updated with saved images.")
def show_image_popup(self, instance):
# 이미지 팝업 창 생성
popup_layout = BoxLayout(orientation='vertical')
img = Image(source=instance.text)
popup_layout.add_widget(img)
# 닫기 버튼 추가
close_btn = Button(text='CLOSE', size_hint_y=None, height=40)
close_btn.bind(on_release=lambda x: popup.dismiss())
popup_layout.add_widget(close_btn)
# 삭제 버튼 추가
delete_btn = Button(text='DELETE', size_hint_y=None, height=40)
delete_btn.bind(on_release=lambda x: self.delete_image(instance, popup))
popup_layout.add_widget(delete_btn)
# 팝업 생성 및 열기
popup = Popup(title=instance.text, content=popup_layout, size_hint=(0.8, 0.8))
popup.open()
print(f"[INFO] Popup opened for image: {instance.text}")
def delete_image(self, instance, popup):
# 이미지 삭제
filename = instance.text
if os.path.exists(filename):
os.remove(filename)
print(f"[INFO] Image deleted: {filename}")
self.update_image_list() # 이미지 리스트 업데이트
else:
print(f"[WARNING] File not found: {filename}")
popup.dismiss()
def stop_app(self, instance):
# 애플리케이션 종료
print("[INFO] Application is shutting down.")
App.get_running_app().stop()
def on_stop(self):
# 애플리케이션이 종료될 때 카메라 해제
if self.capture.isOpened():
print("[INFO] Releasing camera resource.")
self.capture.release()
else:
print("[INFO] Camera resource already released.")
if __name__ == '__main__':
print("[INFO] Starting CameraApp.")
CameraApp().run()
Kivy와 OpenCV를 이용한 이 파이썬 코드를 Android 앱으로 변환하려면 `buildozer`를 사용해야 합니다. `buildozer`는 파이썬 Kivy 애플리케이션을 Android 또는 iOS 애플리케이션으로 패키징해주는 도구입니다. 아래는 Android 앱으로 변환하는 단계입니다:
1. **buildozer 설치하기**
- Linux를 사용하고 있다고 가정합니다. 먼저 `buildozer`와 `git`을 설치합니다.
```sh
sudo apt update
sudo apt install -y build-essential git python3-pip
pip install buildozer
sudo apt install -y libffi-dev libssl-dev
sudo apt install -y libjpeg-dev zlib1g-dev
pip install cython
```
2. **프로젝트 디렉토리 설정**
- 이 코드를 하나의 디렉토리에 `.py` 파일로 저장합니다 (예: `main.py`).
3. **`buildozer.spec` 파일 생성**
- `buildozer init` 명령을 실행하여 `buildozer.spec` 파일을 생성합니다.
```sh
buildozer init
```
- 이 명령은 프로젝트 디렉토리에 `buildozer.spec` 파일을 만듭니다.
4. **`buildozer.spec` 파일 수정**
- `buildozer.spec` 파일을 열고 다음과 같이 수정합니다:
- `package.domain`: 고유한 도메인 이름을 설정합니다 (예: `org.test.cameraapp`).
- `package.name`: 앱의 이름을 설정합니다 (예: `CameraApp`).
- `source.include_exts`: `py`, `png`, `jpg` 등의 파일 확장자를 포함하도록 합니다.
- `requirements`: 이 줄에 `kivy, opencv`를 추가합니다.
```
requirements = python3, kivy, opencv
```
- `android.permissions`: 카메라 사용을 위해 `CAMERA` 권한을 추가합니다.
```
android.permissions = CAMERA, WRITE_EXTERNAL_STORAGE
```
- 필요에 따라 다른 설정도 조정할 수 있습니다.
5. **Android APK 생성**
- `buildozer -v android debug` 명령을 실행하여 APK 파일을 생성합니다.
```sh
buildozer -v android debug
```
- 이 과정에는 시간이 걸릴 수 있으며, 모든 종속성과 SDK를 자동으로 다운로드합니다.
- 완료되면 `bin` 폴더에 APK 파일이 생성됩니다.
6. **APK 파일 실행**
- 생성된 APK 파일을 Android 기기에 설치하고 테스트합니다.
이 과정에서 `buildozer`는 필요한 모든 Android 종속성과 빌드 도구를 다운로드하므로 시간이 오래 걸릴 수 있습니다. 이때 발생할 수 있는 오류는 로그를 통해 확인하고 적절히 대응해야 합니다. 특히 OpenCV와 관련된 빌드 문제는 종종 발생하므로, 필요하다면 `requirements`에 올바른 버전을 지정하거나 추가 설정이 필요할 수 있습니다.