feat: 添加 USB 摄像头连接功能

- 新增 Android USB 摄像头 APP (MJPEG 服务器)
- 电脑端支持 ADB 端口转发连接
- 修复 .gitignore 忽略 Android 文件

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
let5sne.win10
2026-02-12 22:23:43 +08:00
parent 35d05d4701
commit 767271d499
652 changed files with 28034 additions and 22 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

View File

@@ -0,0 +1,2 @@
#Thu Feb 12 21:38:21 CST 2026
gradle.version=9.2.1

Binary file not shown.

View File

@@ -0,0 +1,2 @@
#Thu Feb 12 21:01:39 CST 2026
java.home=C\:\\Program Files\\Android\\Android Studio\\jbr

Binary file not shown.

View File

3
android-app/.idea/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,3 @@
# Default ignored files
/shelf/
/workspace.xml

1
android-app/.idea/.name generated Normal file
View File

@@ -0,0 +1 @@
UsbWebcam

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="AndroidProjectSystem">
<option name="providerId" value="com.android.tools.idea.GradleProjectSystem" />
</component>
</project>

View File

@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="AppInsightsSettings">
<option name="tabSettings">
<map>
<entry key="Firebase Crashlytics">
<value>
<InsightsFilterSettings>
<option name="connection">
<ConnectionSetting>
<option name="appId" value="PLACEHOLDER" />
<option name="mobileSdkAppId" value="" />
<option name="projectId" value="" />
<option name="projectNumber" value="" />
</ConnectionSetting>
</option>
<option name="signal" value="SIGNAL_UNSPECIFIED" />
<option name="timeIntervalDays" value="THIRTY_DAYS" />
<option name="visibilityType" value="ALL" />
</InsightsFilterSettings>
</value>
</entry>
</map>
</option>
</component>
</project>

File diff suppressed because it is too large Load Diff

6
android-app/.idea/compiler.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<bytecodeTargetLevel target="21" />
</component>
</project>

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="deploymentTargetSelector">
<selectionStates>
<SelectionState runConfigName="app">
<option name="selectionMode" value="DROPDOWN" />
</SelectionState>
</selectionStates>
</component>
</project>

18
android-app/.idea/gradle.xml generated Normal file
View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GradleMigrationSettings" migrationVersion="1" />
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<option name="testRunner" value="CHOOSE_PER_TEST" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />
<option value="$PROJECT_DIR$/app" />
</set>
</option>
</GradleProjectSettings>
</option>
</component>
</project>

8
android-app/.idea/markdown.xml generated Normal file
View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="MarkdownSettings">
<option name="previewPanelProviderInfo">
<ProviderInfo name="Compose (experimental)" className="com.intellij.markdown.compose.preview.ComposePanelProvider" />
</option>
</component>
</project>

10
android-app/.idea/migrations.xml generated Normal file
View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectMigrations">
<option name="MigrateToGradleLocalJavaHome">
<set>
<option value="$PROJECT_DIR$" />
</set>
</option>
</component>
</project>

10
android-app/.idea/misc.xml generated Normal file
View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="jbr-21" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">
<option name="id" value="Android" />
</component>
</project>

17
android-app/.idea/runConfigurations.xml generated Normal file
View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RunConfigurationProducerService">
<option name="ignoredProducers">
<set>
<option value="com.intellij.execution.junit.AbstractAllInDirectoryConfigurationProducer" />
<option value="com.intellij.execution.junit.AllInPackageConfigurationProducer" />
<option value="com.intellij.execution.junit.PatternConfigurationProducer" />
<option value="com.intellij.execution.junit.TestInClassConfigurationProducer" />
<option value="com.intellij.execution.junit.UniqueIdConfigurationProducer" />
<option value="com.intellij.execution.junit.testDiscovery.JUnitTestDiscoveryConfigurationProducer" />
<option value="org.jetbrains.kotlin.idea.junit.KotlinJUnitRunConfigurationProducer" />
<option value="org.jetbrains.kotlin.idea.junit.KotlinPatternConfigurationProducer" />
</set>
</option>
</component>
</project>

6
android-app/.idea/vcs.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$/.." vcs="Git" />
</component>
</project>

160
android-app/.idea/workspace.xml generated Normal file
View File

@@ -0,0 +1,160 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="AndroidLayouts">
<shared>
<config />
</shared>
</component>
<component name="AutoImportSettings">
<option name="autoReloadType" value="NONE" />
</component>
<component name="ChangeListManager">
<list default="true" id="129a9e13-1934-4081-883b-3b7d2dedc07d" name="Changes" comment="">
<change afterPath="$PROJECT_DIR$/gradle/wrapper/gradle-wrapper.properties" afterDir="false" />
<change beforePath="$PROJECT_DIR$/../src/desktop.py" beforeDir="false" afterPath="$PROJECT_DIR$/../src/desktop.py" afterDir="false" />
</list>
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
<option name="LAST_RESOLUTION" value="IGNORE" />
</component>
<component name="ClangdSettings">
<option name="formatViaClangd" value="false" />
</component>
<component name="ExecutionTargetManager" SELECTED_TARGET="device_and_snapshot_combo_box_target[PhysicalDevice::serial=GCQ5T18A28009525]" />
<component name="ExternalProjectsData">
<projectState path="$PROJECT_DIR$">
<ProjectState />
</projectState>
</component>
<component name="Git.Settings">
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$/.." />
</component>
<component name="ProjectColorInfo">{
&quot;associatedIndex&quot;: 8
}</component>
<component name="ProjectId" id="39ZQq1pc0IJc1pPrRFgQMIEiZgX" />
<component name="ProjectViewState">
<option name="hideEmptyMiddlePackages" value="true" />
<option name="showLibraryContents" value="true" />
</component>
<component name="PropertiesComponent"><![CDATA[{
"keyToString": {
"Android App.app.executor": "Run",
"Gradle.Configure Daemon JVM Criteria.executor": "Run",
"GradleDaemonJvmCriteriaMigrationNotification.isNotificationDisabled": "true",
"ModuleVcsDetector.initialDetectionPerformed": "true",
"RunOnceActivity.ShowReadmeOnStart": "true",
"RunOnceActivity.TerminalTabsStorage.copyFrom.TerminalArrangementManager.252": "true",
"RunOnceActivity.cidr.known.project.marker": "true",
"RunOnceActivity.git.unshallow": "true",
"RunOnceActivity.readMode.enableVisualFormatting": "true",
"cf.first.check.clang-format": "false",
"cidr.known.project.marker": "true",
"git-widget-placeholder": "main",
"kotlin-language-version-configured": "true",
"last_opened_file_path": "D:/code/post-ocr/android-app",
"settings.editor.selected.configurable": "templates.modelproviders.configurable"
}
}]]></component>
<component name="RunManager">
<configuration name="app" type="AndroidRunConfigurationType" factoryName="Android App" activateToolWindowBeforeRun="false">
<module name="UsbWebcam.app" />
<option name="ANDROID_RUN_CONFIGURATION_SCHEMA_VERSION" value="1" />
<option name="DEPLOY" value="true" />
<option name="DEPLOY_APK_FROM_BUNDLE" value="false" />
<option name="DEPLOY_AS_INSTANT" value="false" />
<option name="ARTIFACT_NAME" value="" />
<option name="PM_INSTALL_OPTIONS" value="" />
<option name="ALL_USERS" value="false" />
<option name="ALWAYS_INSTALL_WITH_PM" value="false" />
<option name="ALLOW_ASSUME_VERIFIED" value="false" />
<option name="CLEAR_APP_STORAGE" value="false" />
<option name="DYNAMIC_FEATURES_DISABLED_LIST" value="" />
<option name="ACTIVITY_EXTRA_FLAGS" value="" />
<option name="MODE" value="default_activity" />
<option name="RESTORE_ENABLED" value="false" />
<option name="RESTORE_FILE" value="" />
<option name="RESTORE_FRESH_INSTALL_ONLY" value="false" />
<option name="CLEAR_LOGCAT" value="false" />
<option name="SHOW_LOGCAT_AUTOMATICALLY" value="false" />
<option name="TARGET_SELECTION_MODE" value="DEVICE_AND_SNAPSHOT_COMBO_BOX" />
<option name="SELECTED_CLOUD_MATRIX_CONFIGURATION_ID" value="-1" />
<option name="SELECTED_CLOUD_MATRIX_PROJECT_ID" value="" />
<option name="DEBUGGER_TYPE" value="Auto" />
<Auto>
<option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
<option name="SHOW_STATIC_VARS" value="true" />
<option name="WORKING_DIR" value="" />
<option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
<option name="SHOW_OPTIMIZED_WARNING" value="true" />
<option name="ATTACH_ON_WAIT_FOR_DEBUGGER" value="false" />
<option name="DEBUG_SANDBOX_SDK" value="false" />
</Auto>
<Hybrid>
<option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
<option name="SHOW_STATIC_VARS" value="true" />
<option name="WORKING_DIR" value="" />
<option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
<option name="SHOW_OPTIMIZED_WARNING" value="true" />
<option name="ATTACH_ON_WAIT_FOR_DEBUGGER" value="false" />
<option name="DEBUG_SANDBOX_SDK" value="false" />
</Hybrid>
<Java>
<option name="ATTACH_ON_WAIT_FOR_DEBUGGER" value="false" />
<option name="DEBUG_SANDBOX_SDK" value="false" />
</Java>
<Native>
<option name="USE_JAVA_AWARE_DEBUGGER" value="false" />
<option name="SHOW_STATIC_VARS" value="true" />
<option name="WORKING_DIR" value="" />
<option name="TARGET_LOGGING_CHANNELS" value="lldb process:gdb-remote packets" />
<option name="SHOW_OPTIMIZED_WARNING" value="true" />
<option name="ATTACH_ON_WAIT_FOR_DEBUGGER" value="false" />
<option name="DEBUG_SANDBOX_SDK" value="false" />
</Native>
<Profilers>
<option name="ADVANCED_PROFILING_ENABLED" value="false" />
<option name="STARTUP_PROFILING_ENABLED" value="false" />
<option name="STARTUP_CPU_PROFILING_ENABLED" value="false" />
<option name="STARTUP_CPU_PROFILING_CONFIGURATION_NAME" value="Java/Kotlin Method Sample (legacy)" />
<option name="STARTUP_NATIVE_MEMORY_PROFILING_ENABLED" value="false" />
<option name="NATIVE_MEMORY_SAMPLE_RATE_BYTES" value="2048" />
</Profilers>
<option name="DEEP_LINK" value="" />
<option name="ACTIVITY" value="" />
<option name="ACTIVITY_CLASS" value="" />
<option name="SEARCH_ACTIVITY_IN_GLOBAL_SCOPE" value="false" />
<option name="SKIP_ACTIVITY_VALIDATION" value="false" />
<method v="2">
<option name="Android.Gradle.BeforeRunTask" enabled="true" />
</method>
</configuration>
</component>
<component name="TaskManager">
<task active="true" id="Default" summary="Default task">
<changelist id="129a9e13-1934-4081-883b-3b7d2dedc07d" name="Changes" comment="" />
<created>1770901300937</created>
<option name="number" value="Default" />
<option name="presentableId" value="Default" />
<updated>1770901300937</updated>
</task>
<servers />
</component>
<component name="play_dynamic_filters_status">
<option name="appIdToCheckInfo">
<map>
<entry key="com.usbwebcam">
<value>
<CheckInfo lastCheckTimestamp="1770903494215" />
</value>
</entry>
<entry key="com.usbwebcam.test">
<value>
<CheckInfo lastCheckTimestamp="1770903494216" />
</value>
</entry>
</map>
</option>
</component>
</project>

112
android-app/README.md Normal file
View File

@@ -0,0 +1,112 @@
# USB 摄像头连接方案
## 概述
通过 USB 数据线将 Android 手机作为摄像头连接到电脑,无需 WiFi/网络。
## 架构
```
┌─────────────────┐ USB ┌─────────────────┐
│ Android APP │ ◄─────────────► │ 电脑 ADB │
│ (MJPEG服务) │ adb forward │ desktop.py │
│ 端口 8080 │ │ localhost:8080 │
└─────────────────┘ └─────────────────┘
```
## 使用步骤
### 1. 准备 Android APP
#### 方式A: 使用 Android Studio 编译(推荐)
1. 安装 Android Studio
2. 打开 `android-app` 目录
3. 连接手机或启动模拟器
4. 点击 Run 按钮
#### 方式B: 下载预编译 APK
如需预编译 APK请联系开发者或自行编译。
### 2. 手机端操作
1. **安装并启动**「USB摄像头」APP
2. **开启 USB 调试**
- 设置 → 关于手机 → 连续点击"版本号" 7次
- 设置 → 开发者选项 → USB调试开启
3. 点击 APP 中的「启动」按钮
4. 屏幕显示:"服务运行中 端口: 8080"
### 3. 电脑端操作
#### 安装 ADB
```bash
# Windows: 下载 platform-tools
# https://developer.android.com/tools/releases/platform-tools
# 或使用 winget
winget install Google.PlatformTools
# 验证安装
adb version
```
#### 连接手机
```bash
# 1. USB 连接手机,手机上弹出"允许USB调试"时点击"允许"
# 2. 验证连接
adb devices
# 应显示类似:
# List of devices attached
# XXXXXXXX device
```
#### 运行桌面程序
```bash
cd d:\code\post-ocr
py -3.12 src\desktop.py
```
#### 连接摄像头
1. 在程序中点击 **"🔌 USB连接"** 按钮
2. 程序会自动执行 `adb forward tcp:8080 tcp:8080`
3. 连接成功后显示实时画面
## 工作原理
1. 手机 APP 启动 MJPEG 流服务器(监听 8080 端口)
2. ADB 将手机端口转发到电脑:`adb forward tcp:8080 tcp:8080`
3. 电脑 OpenCV 读取:`cv2.VideoCapture("http://localhost:8080")`
4. 画面实时显示,支持拍照识别
## 故障排查
### 问题ADB 找不到设备
- 检查 USB 线是否支持数据传输(非仅充电线)
- 手机上是否允许 USB 调试
- 尝试更换 USB 端口
### 问题:连接失败
- 确保 APP 已启动并显示"服务运行中"
- 检查端口 8080 是否被占用
- 尝试重启 APP
### 问题:画面卡顿
- 降低分辨率:在 CameraHelper.kt 中修改预览尺寸
- 检查 USB 线质量
## 技术栈
- **Android**: Kotlin + Camera2 API
- **网络**: MJPEG over HTTP
- **电脑端**: Python + OpenCV + PyQt6
- **通信**: ADB 端口转发

View File

@@ -0,0 +1,35 @@
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
android {
namespace 'com.usbwebcam'
compileSdk 34
defaultConfig {
applicationId "com.usbwebcam"
minSdk 24
targetSdk 34
versionCode 1
versionName "1.0"
}
buildTypes {
release {
minifyEnabled false
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
}
dependencies {
implementation 'androidx.core:core-ktx:1.12.0'
implementation 'androidx.appcompat:appcompat:1.6.1'
}

View File

@@ -0,0 +1,24 @@
com.usbwebcam.app-jetified-savedstate-1.2.0-0 C:\Users\yuanjian\.gradle\caches\9.2.1\transforms\00533b6e62ca19b9d1e9eeb219743fb2\workspace\transformed\jetified-savedstate-1.2.0\res
com.usbwebcam.app-jetified-emoji2-1.2.0-1 C:\Users\yuanjian\.gradle\caches\9.2.1\transforms\1216682855f5510bed95f4067697479b\workspace\transformed\jetified-emoji2-1.2.0\res
com.usbwebcam.app-fragment-1.3.6-2 C:\Users\yuanjian\.gradle\caches\9.2.1\transforms\160536bb06a29642be8f4a00bcf8e4d6\workspace\transformed\fragment-1.3.6\res
com.usbwebcam.app-jetified-lifecycle-process-2.4.1-3 C:\Users\yuanjian\.gradle\caches\9.2.1\transforms\267daa7394ed2025de47a9ccaee77d63\workspace\transformed\jetified-lifecycle-process-2.4.1\res
com.usbwebcam.app-lifecycle-viewmodel-2.5.1-4 C:\Users\yuanjian\.gradle\caches\9.2.1\transforms\32ad9078e111c60bbf712643112fbab6\workspace\transformed\lifecycle-viewmodel-2.5.1\res
com.usbwebcam.app-jetified-lifecycle-viewmodel-savedstate-2.5.1-5 C:\Users\yuanjian\.gradle\caches\9.2.1\transforms\3405e8d22e67a145e3734e26810d7cd3\workspace\transformed\jetified-lifecycle-viewmodel-savedstate-2.5.1\res
com.usbwebcam.app-lifecycle-livedata-core-2.5.1-6 C:\Users\yuanjian\.gradle\caches\9.2.1\transforms\3b2fe76c0932fd2a2a9a9099b4b2eae9\workspace\transformed\lifecycle-livedata-core-2.5.1\res
com.usbwebcam.app-appcompat-1.6.1-7 C:\Users\yuanjian\.gradle\caches\9.2.1\transforms\69c94489aa221d559d8b95bf8096e116\workspace\transformed\appcompat-1.6.1\res
com.usbwebcam.app-jetified-appcompat-resources-1.6.1-8 C:\Users\yuanjian\.gradle\caches\9.2.1\transforms\6f282b62405120f6d1c38cd82054c8b3\workspace\transformed\jetified-appcompat-resources-1.6.1\res
com.usbwebcam.app-jetified-activity-1.6.0-9 C:\Users\yuanjian\.gradle\caches\9.2.1\transforms\9a01765a97504d971a3b2f96b04c2cae\workspace\transformed\jetified-activity-1.6.0\res
com.usbwebcam.app-jetified-core-ktx-1.12.0-10 C:\Users\yuanjian\.gradle\caches\9.2.1\transforms\b836325e73913a9b0cab3cd3d19dd33f\workspace\transformed\jetified-core-ktx-1.12.0\res
com.usbwebcam.app-jetified-startup-runtime-1.1.1-11 C:\Users\yuanjian\.gradle\caches\9.2.1\transforms\d7dc6c1bd6cd5b38fce048531a2f0c49\workspace\transformed\jetified-startup-runtime-1.1.1\res
com.usbwebcam.app-jetified-emoji2-views-helper-1.2.0-12 C:\Users\yuanjian\.gradle\caches\9.2.1\transforms\d8a9ed6d9117b725c4017068e1011df4\workspace\transformed\jetified-emoji2-views-helper-1.2.0\res
com.usbwebcam.app-jetified-annotation-experimental-1.3.0-13 C:\Users\yuanjian\.gradle\caches\9.2.1\transforms\e18733406dbb95e736010ec28ae8e406\workspace\transformed\jetified-annotation-experimental-1.3.0\res
com.usbwebcam.app-lifecycle-runtime-2.5.1-14 C:\Users\yuanjian\.gradle\caches\9.2.1\transforms\f02fc4508e535b715bed38aeb40d0847\workspace\transformed\lifecycle-runtime-2.5.1\res
com.usbwebcam.app-core-1.12.0-15 C:\Users\yuanjian\.gradle\caches\9.2.1\transforms\f816649ce4c661525cb38e8f74354319\workspace\transformed\core-1.12.0\res
com.usbwebcam.app-pngs-16 D:\code\post-ocr\android-app\app\build\generated\res\pngs\debug
com.usbwebcam.app-resValues-17 D:\code\post-ocr\android-app\app\build\generated\res\resValues\debug
com.usbwebcam.app-updated_navigation_xml-18 D:\code\post-ocr\android-app\app\build\generated\updated_navigation_xml\debug
com.usbwebcam.app-packageDebugResources-19 D:\code\post-ocr\android-app\app\build\intermediates\incremental\debug\packageDebugResources\merged.dir
com.usbwebcam.app-packageDebugResources-20 D:\code\post-ocr\android-app\app\build\intermediates\incremental\debug\packageDebugResources\stripped.dir
com.usbwebcam.app-debug-21 D:\code\post-ocr\android-app\app\build\intermediates\merged_res\debug\mergeDebugResources
com.usbwebcam.app-debug-22 D:\code\post-ocr\android-app\app\src\debug\res
com.usbwebcam.app-main-23 D:\code\post-ocr\android-app\app\src\main\res

View File

@@ -0,0 +1,21 @@
{
"version": 3,
"artifactType": {
"type": "APK",
"kind": "Directory"
},
"applicationId": "com.usbwebcam",
"variantName": "debug",
"elements": [
{
"type": "SINGLE",
"filters": [],
"attributes": [],
"versionCode": 1,
"versionName": "1.0",
"outputFile": "app-debug.apk"
}
],
"elementType": "File",
"minSdkVersionForDexing": 24
}

View File

@@ -0,0 +1,2 @@
#- File Locator -
listingFile=../../../apk/debug/output-metadata.json

View File

@@ -0,0 +1,2 @@
appMetadataVersion=1.1
androidGradlePluginVersion=9.0.0

View File

@@ -0,0 +1,10 @@
{
"version": 3,
"artifactType": {
"type": "COMPATIBLE_SCREEN_MANIFEST",
"kind": "Directory"
},
"applicationId": "com.usbwebcam",
"variantName": "debug",
"elements": []
}

Some files were not shown because too many files have changed in this diff Show More