用旧手机DIY远程视频监控:帧差法移动检测与Android源码实战 📅 发布时间:2026/9/1 7:23:52 👁 浏览次数: 简介Android手机远程视频移动检测的完整工程源码演示了如何将手机用作远程视频监控终端通过OpenCV实现画面中的移动目标检测适合具备一定Android基础、希望学习音视频传输与计算机视觉结合的开发者。压缩包共284个文件以Java、XML、Kotlin源码为主并包含Gradle构建脚本、SO库及配置文件整体约55.46MB工程结构完整便于导入Android Studio直接研究。资源围绕摄像头采集、H.264编码、Socket网络传输和GMM/帧差法移动检测等关键模块展开代码注释与模块划分清晰可帮助读者理解从视频预览到远程告警的完整链路。已有275人学习下载对于想落地手机端智能监控原型或参加相关毕设、课设的同学具有直接参考价值。 一部旧手机加这个源码级别的方案就能做成带移动检测的远程视频监控这大概是Android开发里最实用的“小项目”了。我最初做这个是为了看家里猫主子白天在家干什么后来发现很多朋友的需求更实际看老人起夜、盯门口快递、监控商铺角落。项目核心链路就一条端侧采集视频流边编码边做画面差异分析有移动目标出现时再推流或截帧上传其余时间保持低功耗待命。这篇文章我把从源码层面跑通这套系统的完整过程写出来适合有Android基础、想自己搭一套远程监控原型的朋友直接参考。1. 项目概述一部旧手机如何变成带移动检测的远程摄像头1.1 需求场景与整体思路先说清楚这套系统解决什么问题。传统摄像头要么不能自己改算法要么数据全走云端想要“画面里有东西动了才录”这个逻辑成品设备往往要开通额外会员。而在Android平台上你手里一台旧手机就能解决摄像头、编码器、网络模块全都有只需要在App层把“采集—检测—传输—告警”这一条链路串起来。整体架构我拆成四个模块这也是后面读源码时的主线端侧采集通过CameraX或Camera2拿摄像头帧输出YUV数据移动检测对相邻帧做像素级差异分析判断画面是否有明显变化远程传输把视频流通过WebRTC或RTSP推到服务端供手机和浏览器查看告警联动检测到移动后截帧上传、推送通知并自动录制一段视频有人可能会问直接用现成的监控App不就行了确实商业App功能全但两点让人难受一是移动检测的灵敏度不可控二是“本地检测、云端存储、远程查看”这个闭环在商业产品里通常按月收费。自己写源码的好处在于检测算法、推流策略、存储策略全部本地可控哪怕不接云服务也能做到纯内网查看。1.2 技术选型从零写还是改开源项目我的建议是别从零写也别直接用黑盒SDK。最合理的路线是拿一个开源的Android视频监控项目做骨架把移动检测和推流模块替换成自己的实现。我在GitHub上找到过几个不错的参考项目搜motion detection android camera就能找到核心思路基本都是用帧差法加MediaCodec硬编码再配合RTSP或者WebRTC推流。技术栈上我的最终选择是CameraX作为采集框架理由后面细说自研帧差法做移动检测不引入OpenCV减少包体和CPU开销MediaCodec硬编码H.264从底层保证编码效率WebRTC做远程低延迟观看FFmpeg拉流做录制存档这套组合的优点很直接所有模块都有现成的Android API支撑不需要自己写NDK代码也不依赖第三方云服务一台有公网IP的服务器加一部旧手机整套系统就能跑起来。2. 端侧视频采集CameraX与MediaCodec的关键细节2.1 为什么不用Camera2直接写Camera2是Android官方提供的高自由度相机API但它有个典型的坑生命周期管理极其繁琐。摄像头在Activity销毁、应用切后台、系统来电抢摄像头时都会触发各种回调处理不好直接闪退或者黑屏。我早期用Camera2写过一个版本光状态机就维护了五六个状态代码量比功能本身还多。CameraX是Google后来推出的Jetpack相机库它在Camera2之上做了一层封装把生命周期绑定到Activity或Fragment上切后台自动释放摄像头回前台自动重新打开。对咱们这种“长期挂在后台当监控用”的场景这个特性价值极大。再加上ImageAnalysis分析用例可以直接拿到YUV格式的帧数据正好用来做帧差法检测。有个细节要提醒CameraX的ImageAnalysis在拿到YUV帧后默认的输出格式是YUV_420_888。Y通道就是灰度图移动检测只需用Y通道数据不需要转RGB这块能省下不少CPU。2.2 预览、编码与回调节奏采集链路中我建议把“预览”和“分析”拆开处理。预览走PreviewView给用户看画面分析走ImageAnalysis。每个ImageAnalysis回调会拿到一帧YUV数据这里要注意控制分析频率不用每帧都跑检测算法10到15帧每秒就够了太多反而容易误报。编码器这块直接用MediaCodec创建H.264编码器把同样的YUV数据送进去。这里有个参数实测数据可以参考我以720p分辨率、30帧率、1.5Mbps码率为例算过流量每秒码流约187.5KB1.5Mbps除以8每小时约675MB一天24小时不停推流约16GB所以别傻乎乎全天推流这就是为什么移动检测必须在端侧先跑一遍。画面没变化时可以主动跳过编码和推流只维持低帧率“观察模式”这样流量和电量都能大幅下降。3. 移动检测算法帧差法的原理与Android落地3.1 帧差法为什么够用移动检测算法有不少选择最简单的就是帧差法复杂一点的有背景减除再复杂的就是光流法、深度学习目标检测。在Android手机这种算力受限的平台上我的判断是帧差法优先深度学习方案谨慎使用。帧差法的原理一句话就能说清楚对比当前帧和上一帧如果对应位置的像素变化大、且变化面积足够多就判定画面有移动。这个方法有几个天然优势计算量极小不需要训练模型对光照变化的鲁棒性比背景减除好因为相邻两帧时间差短光照突变的影响相对有限。对比其他方案背景减除需要维护背景模型门开个缝、窗帘动一下背景就乱了光流法精度高但计算量大手机上跑实时光流有点吃力YOLO等目标检测能识别“是人还是猫”但需要模型文件、需要GPU或NPU加速适合当进阶方案不适合做基础检测3.2 Kotlin实现与裁剪细节帧差法的实现核心就几行代码但实际操作中要处理很多细节。我封装了一个MotionDetector类核心逻辑分几步取两帧Y通道数据、下采样降噪、逐像素算差值、统计超过阈值的点数、判定是否移动。class MotionDetector(private val width: Int, private val height: Int) { private var prevFrame: ByteArray? null fun isMotion(currentFrame: ByteArray, threshold: Int 30): Boolean { val prev prevFrame if (prev null) { prevFrame currentFrame.copyOf() return false } var diffCount 0 var diffSum 0L val totalPixels width * height // 每隔一个像素采样降低计算量对结果影响很小 var i 0 while (i totalPixels) { val diff Math.abs((currentFrame[i].toInt() and 0xFF) - (prev[i].toInt() and 0xFF)) if (diff threshold) diffCount diffSum diff i 2 } val avgDiff diffSum / (totalPixels / 2) val diffRatio diffCount.toFloat() / (totalPixels / 2) val minChangePixels (totalPixels * 0.01f).toInt() // 至少1%的像素变化 prevFrame currentFrame.copyOf() // 平均亮差大于阈值且变化像素比例达到要求 return avgDiff 12 diffRatio 0.002f diffCount minChangePixels } }这段代码里有几个调参的关键点都是实测踩过的坑threshold参数控制“单像素变化多少才算变化”白天建议30晚上建议15因为晚上画面整体暗像素差绝对值天然偏小至少1%像素变化这个限制是为了过滤噪点不然打开摄像头轻微抖动就会误报采样步长设为2相当于把分析分辨率降为原来的一半速度翻倍准确率几乎不受影响再进一步处理误报的话可以在检测到帧差后对差值区域做膨胀操作把连成片的区域连起来再过滤掉面积过小的独立噪点区。这个操作可以用OpenCV的dilate不想引入OpenCV就自己写个简单的膨胀逻辑遍历像素时对周边3×3邻域做一次最大值替换效果也够用。4. 远程视频传输WebRTC与RTSP的选型权衡4.1 传输方案对比远程视频传输的选型会直接影响整个项目的使用体验。市面上的方案主要分几类我从延迟、开发难度、播放兼容性三个维度对比了一下方案延迟开发难度播放端兼容性适合场景WebRTC200-500ms中浏览器、手机App均可实时交互查看RTSP1-3秒低VLC、ffplay等专用播放器局域网监控RTMP2-5秒中需要播放器支持Flash或H5转封装直播平台HLS5-15秒低浏览器原生video支持录像回放我的方案是WebRTC做实时查看同时服务端用FFmpeg拉流转成HLS做录像回放。原因很简单WebRTC延迟最低而且浏览器不用装任何插件。RTSP虽然部署简单但手机自带浏览器不支持需要额外装VLC之类的App体验割裂。WebRTC的缺点也明显需要信令服务器做连接协商P2P连不通时还需要TURN服务器中转。不过这部分网上有大量现成实现Node.js写个Socket.IO信令服务搭配coturn搭建的STUN/TURN服务就能解决大部分NAT穿透问题。4.2 服务端与Web端观看服务端我跑了两套服务一套是WebRTC信令服务负责转发SDP和ICE候选信息另一套是FFmpeg进程负责把手机推上来的RTSP流重新拉取转成HLS切片供浏览器回放。整个流程我拆开讲一下手机端启动后先连信令服务声明自己的设备IDWeb端打开页面输入设备ID发起观看请求信令服务在Web端和手机端之间交换SDP、ICE信息连接建立后手机端把MediaCodec编码出的H.264裸流通过DataChannel或SRTP发送到Web端Web端用video标签接decoder播放这个过程里最容易出错的是SDP协商时媒体参数不匹配常见症状是黑屏、只有声音没画面、或者花屏。我建议调试时先用官方demo验证端到端链路再替换自己的采集模块不要一上来就全插到自己代码里。5. 源码工程结构与关键模块实现5.1 工程总体结构整套源码如果组织得够清晰后续维护和改造会轻松很多。我最终的工程目录大概这样app/ ├── src/main/java/com/example/remotevidmonitor/ │ ├── camera/ │ │ ├── CameraXManager.kt # 摄像头管理负责启动预览和图像分析 │ │ ├── FrameAnalyzer.kt # 图像分析回调对接移动检测 │ │ └── VideoEncoder.kt # MediaCodec硬编码封装 │ ├── detect/ │ │ ├── MotionDetector.kt # 帧差法移动检测核心算法 │ │ └── DetectConfig.kt # 灵敏度、阈值等配置项 │ ├── network/ │ │ ├── SignalingClient.kt # WebRTC信令客户端 │ │ ├── PeerConnectionManager.kt # WebRTC连接管理 │ │ └── UploadManager.kt # 截帧上传与告警通知 │ └── service/ │ └── MonitorService.kt # 前台监控服务保证后台运行 └── src/main/res/ # 布局与资源这个结构是按模块划分的camera、detect、network三层互不依赖后续想换成OpenCV方案或者想接第三方云服务只需要替换对应模块即可。5.2 关键代码实现与参数说明视频编码器配置这段代码值得单独拿出来说因为MediaCodec的参数设置有一堆坑设置不对轻则编码失败重则花屏val format MediaFormat.createVideoFormat(video/avc, width, height).apply { setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible) setInteger(MediaFormat.KEY_BIT_RATE, 1_500_000) setInteger(MediaFormat.KEY_FRAME_RATE, 30) setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 2) setInteger(MediaFormat.KEY_BITRATE_MODE, MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_VBR) }几个参数的实际意义说一下KEY_I_FRAME_INTERVAL设为2表示每2秒一个关键帧。这个值影响花屏恢复速度网络抖动丢包后要等下一个关键帧画面才能恢复设太大会导致恢复慢设太小又增加码流BITRATE_MODE用VBR可变码率在画面静止时码率大幅下降适合监控场景。CBR恒定码率适合网络带宽严格受限的场景但画面复杂时画质会崩COLOR_FormatYUV420Flexible是兼容性最好的色彩格式各种硬件编码器都支持但实际输入buffer时要做一次柔性格式到具体平面格式的拷贝告警联动这块我实现的是“检测到移动后立即截取当前帧JPEG上传到服务器同时开始录制30秒视频推送到服务端存为回放片段”。检测消息通过推送通道发到用户手机App弹通知。这里有个小技巧截帧用ImageProxy转JPEG时直接复用MediaCodec之前编码的那帧数据不要重新走一遍预览管线能省不少时间。6. 常见问题与排查实战6.1 延迟高、花屏、卡顿的排查思路远程视频监控最常见的吐槽就是“卡成PPT”。我把自己调试时排过的坑整理成了表格现象原因解决方式延迟一直在涨编码参数中GOP过长丢包后恢复慢KEY_I_FRAME_INTERVAL调小到1-2秒画面花屏SDP中rtpmap参数不匹配检查编码分辨率与SDP是否有误卡顿严重手机端CPU被移动检测占满降低分析帧率隔帧分析Web端黑屏H.264 profile级别不一致统一使用Baseline profile网络波动花屏TURN路径带宽不足优先P2PTURN仅作兜底其中最容易忽略的是分辨率匹配问题。我曾经把采集分辨率设为1280×720但MediaCodec编码时配置成了1920×1080结果Web端一直黑屏。排查半天才发现是两处配置不一致。6.2 移动检测误报与后台保活调优移动检测的误报通常来自三个方向光线突变、窗帘飘动、宠物跑跳。光线突变是帧差法的天敌比如阴天某瞬间云层散开整个画面亮度跳变很容易误判。我的应对策略是双重阈值除了单像素阈值再要求“变化区域在形态上是局部连通的”避免全局亮度变化被算作大面积移动。后台保活是另一个大头。Android系统对后台App的杀进程策略很激进尤其国产ROM。我的做法是启动前台服务带常驻通知把进程优先级拉高在服务里Hold住一个WakeLock锁屏后CPU不睡死引导用户把App加入电池优化白名单关键设备上关闭锁屏清理功能并把自启动权限打开最后再说一个源码层面的安全问题。大家拿开源项目改的时候注意看看build.gradle里是否开了混淆release版本要开R8压缩混淆防止别人拿APK反编译看源码。这也是为什么好多人搜“Android反编译去广告”“go反编译能看到源代码吗”这些词反编译这件事在商用项目实施时是必须考虑的。另外带root的设备要谨慎测试Android 14等新版本对前台服务和后台限制更严了适配时要多留意。我前前后后调试了两周最大的感受是这个项目真正难的不是某一个单独的技术点而是通信链路里每一环的参数都要对得上。系统性的做法是从采集到检测到传输逐层验证每一步都拿到可预期的输出再进入下一环节。如果让我重新做一遍我会把移动检测的灵敏度参数做成可在Web端远程调节的配置而不是写死在代码里。这个改动可以放到后续迭代里有兴趣的朋友可以顺着这个方向继续扩展。本文还有配套的精品资源点击获取