均值漂移(MeanShift)目标追踪实现
在计算机视觉中,目标追踪是核心技术之一,而MeanShift(均值漂移)是无需训练模型、轻量高效的经典追踪算法。
它结合直方图反向投影,能在连续帧/不同图片中自动锁定目标位置,广泛应用于移动端人脸、物体、肤色追踪场景。
核心技术原理
1. 算法整体流程
MeanShift 目标追踪分为两大阶段,全程依赖HSV 色调直方图(抗光照干扰,比 BGR 更稳定):
- 训练阶段:在参考图中框选目标(ROI)→ 提取目标的色调(H)直方图作为特征模板;
- 追踪阶段:对新图像做直方图反向投影 → 生成目标概率图 → MeanShift 迭代搜索 → 锁定目标位置。
2. 关键原理详解
(1)为什么用 HSV 色彩空间?
- BGR 空间受亮度影响极大,光线变化会直接导致追踪失效;
- HSV 空间将颜色(色调 H)、饱和度(S)、亮度(V)分离,仅用色调 H描述目标颜色,彻底屏蔽亮度干扰,追踪更稳定。
(2)色调直方图(特征模板)
只提取目标的色调通道直方图,并过滤低饱和度像素(避免灰色/白色干扰),生成唯一的目标颜色特征。
(3)直方图反向投影
用训练好的色调直方图,对新图像逐像素计算匹配概率:
- 像素越接近目标颜色 → 概率越高(图像越亮);
- 像素与目标颜色差异大 → 概率越低(图像越暗)。
(4)MeanShift 均值漂移(核心)
这是算法的灵魂:
- 从初始窗口位置开始;
- 计算窗口内概率图的重心(颜色密度最高的点);
- 将窗口中心移动到重心位置;
- 重复迭代,直到窗口位置不再变化(收敛);
- 最终窗口位置 = 目标真实位置。
简单理解:让窗口自动“飘向”目标颜色最密集的区域。
项目完整实现
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. 拓展方向
- 实时相机追踪:改为相机预览流,实现实时目标追踪;
- CamShift 升级:支持目标缩放/旋转,适配更复杂场景;
- 多目标追踪:同时追踪多个物体;
- 手动框选 ROI:支持手指拖拽选择追踪目标。
总结
本文完整实现了OpenCV MeanShift 目标追踪在 Android 平台的落地,从原理讲解到全项目代码全覆盖,且完全对齐你的项目结构。
MeanShift 作为轻量化追踪算法,非常适合移动端无模型、高性能的图像需求,无论是物体追踪、人脸锁定,还是肤色跟踪,都能快速适配使用。