简介这是一份面向安卓开发方向毕业设计学习者的完整项目源码主题为EyeTracker眼动仪尝试用手机摄像头作为眼动传感器实现目视控制鼠标移动、点击、滚动以及屏幕键盘输入等交互功能为行动不便群体提供低成本的眼球交互方案。资源以zip压缩包形式提供包内共0个文件文件类型明细暂无数据整体大小约21.87MB属于轻量级工程包便于本地解压后直接导入Android Studio阅读与调试。项目关键技术覆盖安卓客户端眼动识别移植、校准流程、鼠标控制逻辑以及基于socket的Windows PC与Android设备通信涉及网络编程与模型精度调优适合具备一定安卓基础、希望深入人机交互或辅助技术方向的本科与高职学生参考。目前已有650人学习下载可作为毕业设计选题、功能拆解与二次开发的实践素材。1. 眼动追踪App源码拆解从安卓端到视线估计的完整落地路径眼动追踪听起来像是实验室里动辄几十万的设备才能干的事但一份安卓毕业设计级别的App源码就能把「用前置摄像头估计视线方向」这条链路跑通。EyeTracker这个项目做的就是这件事调用手机摄像头采集人脸图像在端侧完成人脸检测、关键点定位、眼部区域裁剪再通过一个轻量级模型回归出视线向量最终在屏幕上渲染出注视点。它适合三类人正在做安卓CV方向毕业设计的学生、想了解端侧视线估计工程链路的移动开发者、以及需要一套可运行基线来二次开发的技术团队。整份源码的价值不在于算法有多前沿而在于它把「摄像头采集→人脸对齐→眼部特征→视线回归→UI渲染」这条链路完整地串了起来每个环节都有可替换的接口你可以把任意一环换成自己的模型或方案。2. 工程结构与端侧推理链路先搞清楚数据怎么流2.1 模块划分与依赖关系拿到一份安卓源码第一件事不是急着跑而是先看目录结构。EyeTracker的工程结构大致分为四层采集层、检测层、推理层、渲染层。采集层封装了CameraX的调用负责拿到YUV或RGBA帧检测层通常依赖一个轻量级人脸检测器常见做法是MediaPipe Face Detection或MNN自带的人脸检测推理层是核心负责人脸关键点定位和视线向量回归渲染层则把估计出的注视点映射到屏幕坐标系并绘制。先看依赖配置这是判断项目能不能跑起来的第一道门槛// app/build.gradle 关键依赖 dependencies { // CameraX 用于摄像头采集 implementation androidx.camera:camera-core:1.3.0 implementation androidx.camera:camera-camera2:1.3.0 implementation androidx.camera:camera-lifecycle:1.3.0 // 端侧推理框架常见选择 MNN 或 TFLite implementation com.alibaba:mnn:2.8.0 // 或 implementation org.tensorflow:tensorflow-lite:2.14.0 // 人脸检测与关键点常见做法用 MediaPipe implementation com.google.mediapipe:tasks-vision:0.10.0 }这里有个选型问题值得说清楚为什么端侧推理框架选MNN而不是TFLite如果你的模型是从PyTorch导出的MNN的转换工具链更顺滑而且MNN在ARM CPU上的量化推理性能通常更稳。但如果你的模型本身就是TFLite格式那就没必要多绕一层。源码里如果同时出现了两套推理框架的依赖说明作者可能在做对比实验你只需要保留自己需要的那一套。2.2 从摄像头帧到视线向量的完整调用链数据流的核心逻辑在CameraX的ImageAnalysis用例里。每一帧进来后依次经过格式转换、人脸检测、关键点定位、眼部裁剪、归一化、模型推理、后处理。下面是一个典型的帧处理回调结构// 帧分析回调运行在独立线程 imageAnalysis.setAnalyzer(executor) { imageProxy - // 1. 将 ImageProxy 转为模型可接受的 Bitmap 或 ByteBuffer val bitmap imageProxy.toBitmap() // 常见做法注意旋转角度 // 2. 人脸检测拿到人脸框 val faceRect faceDetector.detect(bitmap) if (faceRect null) { imageProxy.close() returnsetAnalyzer } // 3. 关键点定位提取左右眼中心 val landmarks landmarkDetector.detect(bitmap, faceRect) val leftEye landmarks.leftEyeCenter val rightEye landmarks.rightEyeCenter // 4. 裁剪眼部区域并归一化到模型输入尺寸 val eyePatch cropEyeRegion(bitmap, leftEye, rightEye, targetSize 96) // 5. 模型推理输出视线向量 (pitch, yaw) 或 3D 方向 val gazeVector gazeModel.run(eyePatch) // 6. 将视线向量映射到屏幕坐标并渲染 val screenPoint mapGazeToScreen(gazeVector, faceRect) renderOverlay(screenPoint) imageProxy.close() }这段代码里有几个参数直接决定最终效果。targetSize是眼部裁剪后送入模型的尺寸常见取值是96×96或64×64太小会丢失虹膜细节太大则推理耗时上升。mapGazeToScreen里的映射矩阵需要根据手机屏幕分辨率和摄像头视场角做标定源码里如果写死了某个分辨率换设备就会偏。帧处理线程的executor建议用单线程避免多帧并发导致模型状态混乱。注意CameraX返回的ImageProxy带有旋转信息如果你直接转Bitmap而不处理rotationDegrees人脸检测会在一部分设备上直接失效。这是端侧视觉项目里最常见的翻车点之一。3. 视线估计模型与后处理参数怎么调、精度怎么保3.1 模型输入输出与预处理细节EyeTracker这类项目通常不会从头训练一个视线估计模型而是基于已有的轻量级网络做迁移或直接使用预训练权重。常见的骨干网络是MobileNetV3或ShuffleNetV2输入是双眼区域拼接后的图像输出是一个二维向量pitch, yaw或三维方向向量。预处理环节有几个容易忽略的参数参数常见取值影响输入尺寸96×96 或 64×64越大精度越高推理越慢归一化均值[0.485, 0.456, 0.406]与训练时不一致会导致精度骤降归一化标准差[0.229, 0.224, 0.225]同上眼部裁剪比例1.5倍眼距太小丢失眼角太大引入背景噪声灰度化可选减少计算量但会丢失虹膜颜色信息预处理代码通常长这样# 预处理裁剪 归一化以 Python 伪代码示意 def preprocess(eye_patch, mean, std): # eye_patch: HWC, uint8, RGB img eye_patch.astype(np.float32) / 255.0 img (img - mean) / std # 转为 NCHW 并增加 batch 维度 img np.transpose(img, (2, 0, 1)) img np.expand_dims(img, axis0) return img如果你发现模型输出总是在某个固定方向附近抖动先检查归一化参数是否和训练时一致。很多开源权重用的是ImageNet的均值和标准差但如果你自己训练时用了不同的预处理推理时必须对齐。3.2 后处理与屏幕映射的标定方法模型输出的视线向量是相对于摄像头坐标系的要变成屏幕上的注视点需要经过一次坐标变换。这个变换矩阵取决于摄像头位置、屏幕尺寸、用户距离。源码里如果提供了一个标定流程一定要走一遍如果没有你需要自己补一个简单的五点标定。标定的基本思路是让用户在屏幕上依次注视五个已知位置的点记录每个点对应的模型输出向量然后拟合一个从向量到屏幕坐标的映射矩阵。常见做法是用仿射变换或二次多项式拟合。// 五点标定采集数据后拟合映射矩阵 val calibrationPoints listOf( PointF(0.1f, 0.1f), PointF(0.9f, 0.1f), PointF(0.5f, 0.5f), PointF(0.1f, 0.9f), PointF(0.9f, 0.9f) ) // 对每个点采集 N 帧模型输出并取平均 // 然后用最小二乘法拟合 2x3 仿射矩阵 val affineMatrix fitAffine(calibrationPoints, collectedVectors)标定质量直接决定最终体验。如果标定后注视点仍然偏移很大先检查采集时用户头部是否移动了。头部移动会改变摄像头与眼睛的相对位置导致模型输出漂移。一个实用的技巧是在标定和推理时都加入头部姿态估计当头部姿态变化超过阈值时提示用户重新标定。提示端侧视线估计的精度通常在3到5度视角范围内换算到手机屏幕上大约是1到2厘米的偏移。如果你的应用场景要求更高精度需要考虑红外摄像头或专用硬件方案。4. 避坑与排查那些让项目跑不起来的常见问题4.1 摄像头权限与帧格式导致的崩溃现象App启动后直接闪退日志显示SecurityException或IllegalArgumentException。原因安卓6.0以上需要动态申请CAMERA权限很多毕业设计源码只在Manifest里声明了权限没有在运行时请求。另外CameraX的ImageAnalysis默认输出YUV_420_888格式如果你直接按RGBA处理会拿到错误的数据。解决在Activity或Fragment启动时检查并请求权限在ImageAnalysis.Builder里显式设置setOutputImageFormat(ImageAnalysis.OUTPUT_IMAGE_FORMAT_RGBA_8888)或者写一个YUV转RGB的工具类。4.2 模型文件未打包导致的推理失败现象运行时抛出FileNotFoundException或RuntimeException: Model load failed。原因模型文件放在assets目录下但Gradle配置里没有关闭压缩导致加载时文件损坏。或者模型路径写的是绝对路径换设备后找不到。解决在build.gradle里添加androidResources { noCompress mnn, tflite }加载模型时统一用context.assets.open(model.mnn)读取为ByteBuffer不要用文件路径。4.3 前置摄像头镜像导致的左右眼颠倒现象视线方向左右相反用户看左边但光标往右跑。原因前置摄像头默认输出镜像图像人脸检测拿到的左右眼位置和实际是反的。如果你的模型对左右眼输入顺序敏感输出就会反向。解决在采集层对前置摄像头做水平翻转或者在送入模型前交换左右眼patch的顺序。源码里如果有一个isFrontCamera的布尔标志检查它有没有正确传递到预处理环节。4.4 帧率过低导致的交互卡顿现象光标移动明显滞后帧率低于15fps。原因每帧都在做全图人脸检测关键点视线推理计算量叠加。或者ImageAnalysis的STRATEGY_KEEP_ONLY_LATEST没有设置导致帧堆积。解决设置setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)将人脸检测频率降低到每3帧一次中间帧复用上一次的人脸框对模型做INT8量化通常能提速30%到50%。4.5 不同设备分辨率适配问题现象在一台手机上正常换一台手机后注视点偏移严重。原因屏幕映射矩阵是按固定分辨率算的换设备后摄像头视场角和屏幕比例都变了。解决不要写死分辨率。用DisplayMetrics动态获取屏幕宽高用CameraCharacteristics获取摄像头视场角在运行时重新计算映射矩阵。如果项目里有标定流程换设备后强制重新标定。5. 进阶技巧用INT8量化和多帧平滑把体验拉回可用线5.1 模型量化与推理加速端侧视线估计的模型通常不大但如果不做量化在中低端手机上仍然可能跑不到实时。INT8量化是最直接的加速手段。以MNN为例转换时开启量化# MNN 模型量化转换将 FP32 转为 INT8 ./MNNConvert -f ONNX \ --modelFile gaze_model.onnx \ --MNNModel gaze_model_int8.mnn \ --weightQuantBits 8 \ --bizCode MNN量化后模型体积通常缩小到原来的四分之一推理速度提升30%以上。但要注意量化会带来一定的精度损失视线角度误差可能增加0.5到1度。如果你的应用对精度敏感可以对眼部区域单独用FP16其他层用INT8做混合量化。5.2 多帧平滑与抖动抑制单帧推理的输出往往有抖动直接渲染到屏幕上会让人感觉光标在“发抖”。常见的平滑策略有三种滑动平均、指数平滑、卡尔曼滤波。滑动平均最简单但滞后明显指数平滑折中卡尔曼滤波效果最好但需要调参。// 指数平滑alpha 越小越平滑但滞后越大 var smoothedX 0f var smoothedY 0f val alpha 0.3f fun smooth(rawX: Float, rawY: Float): PairFloat, Float { smoothedX alpha * rawX (1 - alpha) * smoothedX smoothedY alpha * rawY (1 - alpha) * smoothedY return Pair(smoothedX, smoothedY) }alpha的取值需要根据帧率调整。30fps下0.3到0.5之间比较合适如果帧率只有15fpsalpha要调大否则滞后感会非常明显。我一般会先跑一遍原始数据把连续100帧的输出画出来看抖动幅度再决定平滑参数。5.3 验证方法怎么判断你的视线估计是否“能用”不要只看单帧的注视点对不对要设计一个可量化的验证流程。常见做法是在屏幕上随机出现若干个目标点让用户依次注视记录每个目标点上的注视点分布计算平均偏移距离和标准差。如果平均偏移在2厘米以内、标准差在1厘米以内基本可以认为在手机场景下可用。另一个容易被忽略的指标是“首次注视时间”也就是从目标出现到用户注视点进入目标区域的时间。这个指标反映了系统的响应速度和平滑策略的合理性。如果首次注视时间超过500毫秒用户会明显感觉“跟不上”。从那以后我每次拿到端侧视觉项目都会先跑一遍量化前后的精度对比再跑一遍平滑前后的抖动曲线两个数据都过关了才敢往UI上接。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?