refactor: 移除 Web 环境,专注桌面应用,修复 macOS 卡死问题

Web 环境移除:
- 删除 Web 相关文件:src/app.py, heartbeat.py
- 用 requirements-desktop.txt 替换 requirements.txt
- 更新 README.md:移除 Web 界面、部署方案等章节
- 更新技术栈说明:Streamlit → PyQt6
- 添加 usb_bundle/ 到 .gitignore

Desktop 应用改进:
- 重构 OCRService:使用独立 Python 线程替代 QThread
- 添加主线程预加载 paddleocr 模块,修复 macOS 上卡死问题
- 新增离线 OCR 初始化模块(src/ocr_offline.py)
- 新增模型准备脚本(scripts/prepare_models.py)
- 新增摄像头诊断工具(scripts/camera_probe.py)

功能定位:
- Desktop 应用(src/desktop.py):实时摄像头拍照识别
- CLI 批处理(src/main.py):批量处理目录中的图片

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
empty
2026-02-14 17:31:05 +08:00
parent 35d05d4701
commit 0ee00e6be7
10 changed files with 919 additions and 443 deletions

67
scripts/camera_probe.py Executable file
View File

@@ -0,0 +1,67 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
摄像头探测脚本(用于排查 macOS/iPhone 连续互通相机无画面问题)
用法:
source .venv/bin/activate
python scripts/camera_probe.py
输出:
- 列出 0~9 号摄像头是否可打开、是否可读到有效帧、帧尺寸与亮度均值
"""
from __future__ import annotations
import sys
def open_cap(cv2, cam_id: int):
if sys.platform == "darwin" and hasattr(cv2, "CAP_AVFOUNDATION"):
return cv2.VideoCapture(cam_id, cv2.CAP_AVFOUNDATION)
return cv2.VideoCapture(cam_id)
def main() -> int:
import cv2 # pylint: disable=import-error
print(f"平台: {sys.platform}")
print(f"OpenCV: {cv2.__version__}")
print("")
found_any = False
for cam_id in range(10):
cap = open_cap(cv2, cam_id)
opened = cap.isOpened()
ok = False
shape = None
mean = None
if opened:
for _ in range(30):
ret, frame = cap.read()
if ret and frame is not None and frame.size > 0:
ok = True
shape = frame.shape
mean = float(frame.mean())
break
cap.release()
if opened:
found_any = True
status = "OK" if ok else ("打开但无画面" if opened else "无法打开")
print(f"摄像头 {cam_id}: {status}", end="")
if ok:
print(f" | shape={shape} | mean={mean:.1f}")
else:
print("")
if not found_any:
print("\n未检测到可打开的摄像头。")
else:
print("\n如果出现“打开但无画面”,优先检查 macOS 相机权限。")
return 0
if __name__ == "__main__":
raise SystemExit(main())

59
scripts/prepare_models.py Executable file
View File

@@ -0,0 +1,59 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
离线模型准备脚本(建议在“有网机器”执行一次)
用途:
- 将 PaddleOCR 2.10.0PP-OCRv4 中文)所需模型下载到指定 models/ 目录
- 该 models/ 目录可直接随 Windows zip 目录包分发,实现完全离线运行
设计说明:
- 脚本只做“下载/补齐”,不做删除或覆盖,避免误删用户已有模型(高风险操作)
"""
from __future__ import annotations
import argparse
import os
from pathlib import Path
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description="准备 post-ocr 离线模型PP-OCRv4 中文)")
parser.add_argument(
"--models-dir",
default="models",
help="模型输出目录默认models建议与 exe 同级)",
)
parser.add_argument(
"--show-log",
action="store_true",
help="显示 PaddleOCR 初始化日志(默认关闭)",
)
return parser.parse_args()
def main() -> int:
args = parse_args()
models_dir = Path(args.models_dir).resolve()
models_dir.mkdir(parents=True, exist_ok=True)
# 关键:把 PaddleOCR 默认 base_dir 指到我们指定的 models/
os.environ["PADDLE_PDX_DISABLE_MODEL_SOURCE_CHECK"] = "True"
os.environ["PADDLE_OCR_BASE_DIR"] = str(models_dir)
# 延迟导入:确保环境变量在模块加载前生效
from paddleocr import PaddleOCR # pylint: disable=import-error
print(f"将下载/补齐模型到: {models_dir}")
print("首次执行需要联网下载(约数百 MB请耐心等待。")
# 初始化会自动下载 det/rec/cls 模型到 BASE_DIR/whl/...
PaddleOCR(lang="ch", show_log=args.show_log, use_angle_cls=False)
print("完成。你可以将该 models/ 目录随 zip 目录包一起分发(与 exe 同级)。")
return 0
if __name__ == "__main__":
raise SystemExit(main())