news 2026/5/20 4:18:02

【android opencv学习笔记】Day 17: 目标追踪(MeanShift)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【android opencv学习笔记】Day 17: 目标追踪(MeanShift)

均值漂移(MeanShift)目标追踪实现

在计算机视觉中,目标追踪是核心技术之一,而MeanShift(均值漂移)是无需训练模型、轻量高效的经典追踪算法。

它结合直方图反向投影,能在连续帧/不同图片中自动锁定目标位置,广泛应用于移动端人脸、物体、肤色追踪场景。


核心技术原理

1. 算法整体流程

MeanShift 目标追踪分为两大阶段,全程依赖HSV 色调直方图(抗光照干扰,比 BGR 更稳定):

  1. 训练阶段:在参考图中框选目标(ROI)→ 提取目标的色调(H)直方图作为特征模板;
  2. 追踪阶段:对新图像做直方图反向投影 → 生成目标概率图 → MeanShift 迭代搜索 → 锁定目标位置。

2. 关键原理详解

(1)为什么用 HSV 色彩空间?
  • BGR 空间受亮度影响极大,光线变化会直接导致追踪失效;
  • HSV 空间将颜色(色调 H)、饱和度(S)、亮度(V)分离,仅用色调 H描述目标颜色,彻底屏蔽亮度干扰,追踪更稳定。
(2)色调直方图(特征模板)

只提取目标的色调通道直方图,并过滤低饱和度像素(避免灰色/白色干扰),生成唯一的目标颜色特征。

(3)直方图反向投影

用训练好的色调直方图,对新图像逐像素计算匹配概率

  • 像素越接近目标颜色 → 概率越高(图像越亮);
  • 像素与目标颜色差异大 → 概率越低(图像越暗)。
(4)MeanShift 均值漂移(核心)

这是算法的灵魂:

  1. 初始窗口位置开始;
  2. 计算窗口内概率图的重心(颜色密度最高的点)
  3. 将窗口中心移动到重心位置
  4. 重复迭代,直到窗口位置不再变化(收敛);
  5. 最终窗口位置 = 目标真实位置。

简单理解:让窗口自动“飘向”目标颜色最密集的区域


项目完整实现

1. Kotlin 上层代码(MainActivity.kt)

负责图片加载、调用 C++ 算法、结果展示,格式与你的肤色检测代码完全对齐

packagecom.nicoli.helloroidetectskinimportandroid.graphics.Bitmapimportandroid.graphics.BitmapFactoryimportandroid.os.Bundleimportandroid.widget.ImageViewimportandroidx.appcompat.app.AppCompatActivityclassMainActivity:AppCompatActivity(){companionobject{init{// 加载 C++ 原生库System.loadLibrary("native-lib")}}// 1. 训练:传入人脸样本图,学习肤色特征privateexternalfuntrainSkinFeature(sampleBitmap:Bitmap)// 2. 追踪:传入全身图,输出带人脸框的结果privateexternalfuntrackHumanFace(srcBitmap:Bitmap,outBitmap:Bitmap)overridefunonCreate(savedInstanceState:Bundle?){super.onCreate(savedInstanceState)setContentView(R.layout.activity_main)// ========== 1. 加载两张图片 ==========// 样本图(整张都是人脸)valfaceSample=BitmapFactory.decodeResource(resources,R.drawable.face_sample)// 全身待测图valfullBody=BitmapFactory.decodeResource(resources,R.drawable.full_body)// ========== 2. 创建输出位图 ==========valresultBitmap=Bitmap.createBitmap(fullBody.width,fullBody.height,Bitmap.Config.ARGB_8888)// ========== 3. 执行算法流程 ==========trainSkinFeature(faceSample)// 学习肤色trackHumanFace(fullBody,resultBitmap)// 追踪人脸// ========== 4. 显示结果 ==========findViewById<ImageView>(R.id.iv_sample_face).setImageBitmap(faceSample)findViewById<ImageView>(R.id.iv_full_body).setImageBitmap(fullBody)findViewById<ImageView>(R.id.iv_result).setImageBitmap(resultBitmap)}}

2. C++ 算法核心代码(native-lib.cpp)

包含格式转换、色调直方图提取、反向投影、MeanShift 追踪,所有 API 均适配 Android。

// JNI 通信头文件#include<jni.h>// OpenCV 核心库#include<opencv2/opencv.hpp>// Android Bitmap 操作#include<android/bitmap.h>// 通道拆分容器#include<vector>usingnamespacecv;usingnamespacestd;// 全局变量:保存训练好的肤色直方图Mat g_skinHist;// ==========================// 工具函数1:Android Bitmap → OpenCV Mat// 作用:将安卓图像格式转为OpenCV可处理的矩阵// ==========================MatbitmapToMat(JNIEnv*env,jobject bitmap){AndroidBitmapInfo info;void*pixels;// 获取图片信息AndroidBitmap_getInfo(env,bitmap,&info);// 锁定像素内存AndroidBitmap_lockPixels(env,bitmap,&pixels);// 构建 RGBA 格式矩阵Matrgba(info.height,info.width,CV_8UC4,pixels);Mat bgr;// RGBA → BGR(OpenCV 默认格式)cvtColor(rgba,bgr,COLOR_RGBA2BGR);// 解锁内存AndroidBitmap_unlockPixels(env,bitmap);returnbgr;}// ==========================// 工具函数2:OpenCV Mat → Android Bitmap// 作用:将处理后的图像转回安卓可显示格式// ==========================voidmatToBitmap(JNIEnv*env,constMat&srcMat,jobject dstBitmap){AndroidBitmapInfo info;void*pixels;AndroidBitmap_getInfo(env,dstBitmap,&info);AndroidBitmap_lockPixels(env,dstBitmap,&pixels);Mat rgba;// 灰度图 / 彩色图分别转 RGBAif(srcMat.channels()==1)cvtColor(srcMat,rgba,COLOR_GRAY2RGBA);elsecvtColor(srcMat,rgba,COLOR_BGR2RGBA);// 复制像素数据memcpy(pixels,rgba.data,info.width*info.height*4);AndroidBitmap_unlockPixels(env,dstBitmap);}// ==========================// 【核心】从整张人脸图提取肤色直方图// ==========================MatgetSkinHueHist(constMat&src){Mat hsv;// BGR 转 HSV(肤色检测必须用)cvtColor(src,hsv,COLOR_BGR2HSV);// ✅ 整张图都是人脸,直接作为样本Mat faceSrc=hsv;// 拆分 H/S/V 三个通道vector<Mat>chs;split(faceSrc,chs);Mat mask;// 过滤低饱和度杂色(灰色/白色干扰)threshold(chs[1],mask,35,255,THRESH_BINARY);// 直方图参数inthistSize=18;floathueRange[]={0,180};constfloat*ranges[]={hueRange};intchannels[]={0};// 只使用 H 通道Mat hist;// 计算一维色调直方图calcHist(&faceSrc,1,channels,mask,hist,1,&histSize,ranges);// 归一化到 0~255normalize(hist,hist,0,255,NORM_MINMAX);returnhist;}// ==========================// JNI 接口1:训练肤色特征// ==========================extern"C"JNIEXPORTvoidJNICALLJava_com_nicoli_helloroidetectskin_MainActivity_trainSkinFeature(JNIEnv*env,jobject thiz,jobject sampleBitmap){Mat sampleMat=bitmapToMat(env,sampleBitmap);// 训练并保存肤色直方图g_skinHist=getSkinHueHist(sampleMat);}// ==========================// JNI 接口2:MeanShift 人脸追踪// ==========================extern"C"JNIEXPORTvoidJNICALLJava_com_nicoli_helloroidetectskin_MainActivity_trackHumanFace(JNIEnv*env,jobject thiz,jobject srcBitmap,jobject outBitmap){Mat src=bitmapToMat(env,srcBitmap);Mat hsv;cvtColor(src,hsv,COLOR_BGR2HSV);// 反向投影:生成肤色概率图floathueRange[]={0,180};constfloat*ranges[]={hueRange};intch[]={0};Mat backProj;calcBackProject(&hsv,1,ch,g_skinHist,backProj,ranges,1.0);// 初始搜索窗口(画面上半部分)RectsearchRect(src.cols*0.3f,src.rows*0.05f,src.cols*0.4f,src.rows*0.35f);// MeanShift 停止条件TermCriteriacriteria(TermCriteria::EPS|TermCriteria::MAX_ITER,15,2);// 执行追踪meanShift(backProj,searchRect,criteria);// 复制原图并绘制绿色框Mat result=src.clone();rectangle(result,searchRect,Scalar(0,255,0),3);// 输出结果matToBitmap(env,result,outBitmap);}

3. 布局文件(activity_main.xml)

展示训练图、追踪图、结果图

<?xml version="1.0" encoding="utf-8"?><LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"android:padding="8dp"><!-- 1. 人脸样本图:整张都是人脸 --><ImageViewandroid:id="@+id/iv_sample_face"android:layout_width="match_parent"android:layout_height="0dp"android:layout_weight="1"android:scaleType="fitCenter"android:adjustViewBounds="true"/><!-- 2. 全身原图 --><ImageViewandroid:id="@+id/iv_full_body"android:layout_width="match_parent"android:layout_height="0dp"android:layout_weight="1"android:layout_marginTop="4dp"android:scaleType="fitCenter"android:adjustViewBounds="true"/><!-- 3. 追踪结果:带绿色人脸框 --><ImageViewandroid:id="@+id/iv_result"android:layout_width="match_parent"android:layout_height="0dp"android:layout_weight="1"android:layout_marginTop="4dp"android:scaleType="fitCenter"android:adjustViewBounds="true"/></LinearLayout>

项目优势与拓展方向

1. 核心优势

  • 轻量无模型:无需深度学习,无权重文件,包体极小;
  • 抗光照干扰:基于 HSV 色调,亮度变化不影响效果;
  • 高性能:C++ 底层运算,适配 Android 移动端;
  • 易集成:与你的肤色检测代码结构统一,直接合并项目。

2. 拓展方向

  1. 实时相机追踪:改为相机预览流,实现实时目标追踪;
  2. CamShift 升级:支持目标缩放/旋转,适配更复杂场景;
  3. 多目标追踪:同时追踪多个物体;
  4. 手动框选 ROI:支持手指拖拽选择追踪目标。

总结

本文完整实现了OpenCV MeanShift 目标追踪在 Android 平台的落地,从原理讲解全项目代码全覆盖,且完全对齐你的项目结构。

MeanShift 作为轻量化追踪算法,非常适合移动端无模型、高性能的图像需求,无论是物体追踪、人脸锁定,还是肤色跟踪,都能快速适配使用。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/20 4:16:11

Langchain的学习(一)

目录 一,实操 编码 Runnable Runnable 是什么 核心方法(所有 Runnable 都有) 最关键能力:用 | 组合(LCEL) 常用内置 Runnable 总结 二,聊天模型-核心能力 定义模型 init_chat_model 本地部署 调用工具 定义工具-Tool version1 schema: version2(基于…

作者头像 李华
网站建设 2026/5/20 4:14:21

JavaEE进阶:MyBatis 操作数据库(入门)

1.什么是MyBatis?MyBatis是一款优秀的持久层框架,用于简化JDBC的开发.持久层,指的就是持久化操作的层,通常是指数据访问层.用来操作数据库.简单来说,MyBatis是更简单完成程序和数据库交互的框架,也就是更简单操作和读取数据库的工具.接下来我们就通过一个入门的程序,感受一下M…

作者头像 李华
网站建设 2026/5/20 4:13:11

ARM PMU原理与缓存性能优化实战

1. ARM PMU基础概念与工作原理性能监控单元(PMU)是现代处理器中用于硬件事件统计的关键模块&#xff0c;在ARM架构中扮演着系统性能分析的重要角色。PMU通过专用计数器实时记录各类微架构事件&#xff0c;为开发者提供底层硬件行为的可视化窗口。1.1 PMU的硬件实现机制ARM PMU通…

作者头像 李华
网站建设 2026/5/20 4:13:10

量子能量隐形传态(QET)原理与应用解析

1. 量子能量隐形传态&#xff08;QET&#xff09;基础解析量子能量隐形传态&#xff08;Quantum Energy Teleportation, QET&#xff09;是近年来量子信息科学领域最具突破性的概念之一。与传统的量子隐形传态&#xff08;Quantum Teleportation&#xff09;不同&#xff0c;QE…

作者头像 李华