【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++ 算法、结果展示,格式与你的肤色检测代码完全对齐

kotlin 复制代码
package com.nicoli.helloroidetectskin

import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.os.Bundle
import android.widget.ImageView
import androidx.appcompat.app.AppCompatActivity

class MainActivity : AppCompatActivity() {
    companion object {
        init {
            // 加载 C++ 原生库
            System.loadLibrary("native-lib")
        }
    }

    // 1. 训练:传入人脸样本图,学习肤色特征
    private external fun trainSkinFeature(sampleBitmap: Bitmap)

    // 2. 追踪:传入全身图,输出带人脸框的结果
    private external fun trackHumanFace(srcBitmap: Bitmap, outBitmap: Bitmap)

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // ========== 1. 加载两张图片 ==========
        // 样本图(整张都是人脸)
        val faceSample = BitmapFactory.decodeResource(resources, R.drawable.face_sample)
        // 全身待测图
        val fullBody = BitmapFactory.decodeResource(resources, R.drawable.full_body)

        // ========== 2. 创建输出位图 ==========
        val resultBitmap = 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。

cpp 复制代码
// JNI 通信头文件
#include <jni.h>
// OpenCV 核心库
#include <opencv2/opencv.hpp>
// Android Bitmap 操作
#include <android/bitmap.h>
// 通道拆分容器
#include <vector>

using namespace cv;
using namespace std;

// 全局变量:保存训练好的肤色直方图
Mat g_skinHist;

// ==========================
// 工具函数1:Android Bitmap → OpenCV Mat
// 作用:将安卓图像格式转为OpenCV可处理的矩阵
// ==========================
Mat bitmapToMat(JNIEnv *env, jobject bitmap) {
    AndroidBitmapInfo info;
    void* pixels;

    // 获取图片信息
    AndroidBitmap_getInfo(env, bitmap, &info);
    // 锁定像素内存
    AndroidBitmap_lockPixels(env, bitmap, &pixels);

    // 构建 RGBA 格式矩阵
    Mat rgba(info.height, info.width, CV_8UC4, pixels);
    Mat bgr;

    // RGBA → BGR(OpenCV 默认格式)
    cvtColor(rgba, bgr, COLOR_RGBA2BGR);

    // 解锁内存
    AndroidBitmap_unlockPixels(env, bitmap);
    return bgr;
}

// ==========================
// 工具函数2:OpenCV Mat → Android Bitmap
// 作用:将处理后的图像转回安卓可显示格式
// ==========================
void matToBitmap(JNIEnv *env, const Mat& srcMat, jobject dstBitmap) {
    AndroidBitmapInfo info;
    void* pixels;

    AndroidBitmap_getInfo(env, dstBitmap, &info);
    AndroidBitmap_lockPixels(env, dstBitmap, &pixels);

    Mat rgba;

    // 灰度图 / 彩色图分别转 RGBA
    if(srcMat.channels() == 1)
        cvtColor(srcMat, rgba, COLOR_GRAY2RGBA);
    else
        cvtColor(srcMat, rgba, COLOR_BGR2RGBA);

    // 复制像素数据
    memcpy(pixels, rgba.data, info.width * info.height * 4);
    AndroidBitmap_unlockPixels(env, dstBitmap);
}

// ==========================
// 【核心】从整张人脸图提取肤色直方图
// ==========================
Mat getSkinHueHist(const Mat& 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);

    // 直方图参数
    int histSize = 18;
    float hueRange[] = {0,180};
    const float* ranges[] = {hueRange};
    int channels[] = {0}; // 只使用 H 通道

    Mat hist;
    // 计算一维色调直方图
    calcHist(&faceSrc,1,channels,mask,hist,1,&histSize,ranges);
    // 归一化到 0~255
    normalize(hist,hist,0,255,NORM_MINMAX);

    return hist;
}

// ==========================
// JNI 接口1:训练肤色特征
// ==========================
extern "C" JNIEXPORT void JNICALL
Java_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" JNIEXPORT void JNICALL
Java_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);

    // 反向投影:生成肤色概率图
    float hueRange[]={0,180};
    const float* ranges[]={hueRange};
    int ch[]={0};
    Mat backProj;

    calcBackProject(&hsv,1,ch,g_skinHist,backProj,ranges,1.0);

    // 初始搜索窗口(画面上半部分)
    Rect searchRect(
            src.cols * 0.3f,
            src.rows * 0.05f,
            src.cols * 0.4f,
            src.rows * 0.35f
    );

    // MeanShift 停止条件
    TermCriteria criteria(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 复制代码
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns: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. 人脸样本图:整张都是人脸 -->
    <ImageView
        android: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. 全身原图 -->
    <ImageView
        android: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. 追踪结果:带绿色人脸框 -->
    <ImageView
        android: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 作为轻量化追踪算法,非常适合移动端无模型、高性能的图像需求,无论是物体追踪、人脸锁定,还是肤色跟踪,都能快速适配使用。

相关推荐
一只机电自动化菜鸟3 小时前
一建机电备考笔记(40) 建筑机电施工—排水管道施工(含考频+题型)
经验分享·笔记·学习·职场和发展·课程设计
2301_818730563 小时前
numpy的学习(笔记)
学习·numpy
用户86022504674723 小时前
AI 分析头部APP系统优化框架
android
用户86022504674723 小时前
AI分析头部APP优化框架
android
GHL2842710903 小时前
Logon failed, use ctrl+c to cancel basic credential prompt
学习·prompt
共享家95273 小时前
Langchain的学习(二)
学习·langchain
victory04314 小时前
agent 学习路径解析 学习资源分享
学习
咸甜适中5 小时前
rust语言学习笔记Trait(八)Iterator(迭代器)
笔记·学习·rust
华为云开发者联盟6 小时前
告别繁琐操作,华为云码道 + Docker重塑远程开发体验
人工智能·学习·docker·华为云·软件开发·华为云码道