Dish_Recognition/usb_camera_app.py

146 lines
4.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# usb_camera_app.py
# 菜品识别:仅 USB 相机(UI 复用 ui.classifier_ui)
import os
import sys
import threading
import time
import cv2
if hasattr(sys, 'frozen'):
os.environ['PYTHONIOENCODING'] = 'utf-8'
from ui.classifier_ui import BaseDishApp, run_app
def _open_usb_capture(index=0):
"""Windows 优先 DirectShow,避免默认 MSMF 打开要几十秒。"""
candidates = []
if sys.platform.startswith('win'):
candidates.append(('DSHOW', cv2.CAP_DSHOW))
candidates.append(('MSMF', cv2.CAP_MSMF))
candidates.append(('ANY', cv2.CAP_ANY))
last_err = None
for name, backend in candidates:
cap = None
try:
t0 = time.perf_counter()
cap = cv2.VideoCapture(index, backend)
if not cap.isOpened():
cap.release()
continue
cap.set(cv2.CAP_PROP_BUFFERSIZE, 1)
cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)
# 读一帧确认真正可用,并丢掉打开时的黑帧
ok, frame = cap.read()
elapsed = time.perf_counter() - t0
if ok and frame is not None and frame.size > 0:
print(f'[OK] USB camera opened via {name} in {elapsed:.2f}s')
return cap
cap.release()
except Exception as exc:
last_err = exc
try:
if cap is not None:
cap.release()
except Exception:
pass
if last_err:
print(f'[WARN] USB open attempts failed: {last_err}')
return None
class ImageClassifierDemo(BaseDishApp):
window_title = "菜品识别系统"
app_title = "菜品识别系统"
edition_label = "USB 相机版"
camera_display_name = "USB相机"
def __init__(self, root):
self.cap = None
self._frame_lock = threading.Lock()
self._cam_ready = threading.Event()
self._cam_ok = False
# 与 YOLO/UI 初始化并行打开相机,缩短出画面时间
threading.Thread(target=self._connect_camera_worker, daemon=True).start()
super().__init__(root)
def _connect_camera_worker(self):
try:
cap = _open_usb_capture(0)
if cap is not None:
self.cap = cap
self._cam_ok = True
else:
self.cap = None
self._cam_ok = False
except Exception as e:
self.cap = None
self._cam_ok = False
print(f"USB相机连接失败:{e}")
finally:
self._cam_ready.set()
def auto_connect_camera(self):
def finish():
self._cam_ready.wait(timeout=90)
if self._cam_ok and self.cap is not None and self.cap.isOpened():
self.camera_connected = True
self.root.after(0, self._update_camera_ui_success)
else:
self.camera_connected = False
self.root.after(0, self._update_camera_ui_fail)
threading.Thread(target=finish, daemon=True).start()
def grab_preview_frame(self):
if not self.camera_connected or self.cap is None:
return None
with self._frame_lock:
try:
# BUFFERSIZE=1 时一次 grab+read 即可拿到较新帧
self.cap.grab()
ret, frame = self.cap.retrieve()
if not ret or frame is None or frame.size == 0:
ret, frame = self.cap.read()
if ret and frame is not None and frame.size > 0:
return frame
except Exception as e:
print(f"USB取帧失败: {e}")
return None
def take_photo(self):
if not self.camera_connected or self.cap is None:
return -1, None
os.makedirs("images", exist_ok=True)
capture_time = time.strftime("%Y%m%d_%H%M%S")
save_path = os.path.abspath(f'images/capture_{capture_time}.jpg')
try:
frame = self.grab_preview_frame()
if frame is None:
return -1, None
if cv2.imwrite(save_path, frame):
return 0, save_path
except Exception as e:
print(f"USB拍照失败: {e}")
return -1, None
def close_camera(self):
if self.cap is not None:
self.cap.release()
self.cap = None
self.camera_connected = False
if __name__ == "__main__":
# 与界面一同启动本机客户端服务(媒体 HTTP :9100;排餐/设备状态在 BaseDishApp 内拉起)
try:
from service.media_server import ensure_media_server
ensure_media_server()
except Exception as exc:
print(f'[WARN] media server start failed: {exc}')
run_app(ImageClassifierDemo)