用均值平移算法查找目标 - sumpig/OpenCV GitHub Wiki


均值平移算法广泛应用于视觉追踪。

假设我们已经识别出一个感兴趣的物体(例如狒狒的脸),如下图所示:

我们希望在第二幅图片中,能追踪到感兴趣物体新的位置。


因为狒狒脸部有非常独特的粉红色,使用像素的色调很容易标识狒狒脸部,因此第一步就是把图像转换成 HSV 色彩空间,计算出 色调直方图

在使用颜色的色调分量时,要把它的饱和度考虑在内(饱和度是向量的第二个入口)。如果颜色的饱和度很低,它的色调信息就会变得不稳定且不可靠。这是因为低饱和度颜色的 B、G 和R 分量几乎是相等的,这导致很难确定它所表示的准确颜色。因此,我们决定忽略低饱和度颜色的色彩分量,也就是不把它们统计进直方图中(在 getHueHistogram 方法中使用 minSat 参数可屏蔽掉饱和度低于此阈值的像素)。

cv::Mat image = cv::imread("baboon01.jpg");

# 狒狒脸部的 ROI
cv::Rect rect(110, 45, 35, 45);
cv::Mat imageROI = image(rect);

# 得到狒狒脸部的直方图
int minSat = 65;
ColorHistogram hc;
cv::Mat colorhist = hc.getHueHistogram(imageROI, minSat);
cv::Mat getHueHistogram(const cv::Mat& image, int minSaturation = 0) {

    cv::Mat hist;

    // 转换成HSV 色彩空间
    cv::Mat hsv;
    cv::cvtColor(image, hsv, cv::COLOR_BGR2HSV);

    // 掩码
    cv::Mat mask;
    
    if (minSaturation > 0) {

        // 将3 个通道分割进3 幅图像
        std::vector<cv::Mat> v;
        cv::split(hsv, v);

        // 屏蔽低饱和度的像素
        cv::threshold(v[1], mask, minSaturation, 255,
            cv::THRESH_BINARY);
    }

    // 准备一维色调直方图的参数
    hranges[0] = 0.0;    // 范围为0~180
    hranges[1] = 180.0;
    channels[0] = 0;    // 色调通道

    // 计算直方图
    cv::calcHist(&hsv,
        1,          // 只有一幅图像的直方图
        channels,   // 用到的通道
        mask,       // 二值掩码 
        hist, // 生成的直方图
        1, // 这是一维直方图
        histSize, // 箱子数量
        ranges // 像素值范围
    );

    return hist;
}

现在打开第二幅图像,然后对第一幅图像的直方图做反向投影。

# 色调直方图作归一化处理
ContentFinder finder;
finder.setHistogram(colorhist);

image= cv::imread("baboon2.jpg");

// 转换成HSV 色彩空间
cv::cvtColor(image, hsv, CV_BGR2HSV);

// 得到色调直方图的反向投影
int ch[1]={0};
finder.setThreshold(-1.0f); // 不做阈值化
cv::Mat result= finder.find(hsv, 0.0f, 180.0f, ch);
void setHistogram(const cv::Mat& h) {

    cv::normalize(h, histogram, 1.0);
}


cv::Mat find(const cv::Mat& image, float minValue, float maxValue, int* channels) {

    cv::Mat result;

    hranges[0] = minValue;
    hranges[1] = maxValue;

    for (int i = 0; i < histogram.dims; i++)
        this->channels[i] = channels[i];

    cv::calcBackProject(&image,
        1,            // 只使用一幅图像
        channels,     // 通道
        histogram,    // 直方图
        result,       // 反向投影的图像
        ranges,       // 每个维度的值范围
        255.0         // 把概率值从1 映射到255
    );

    // 对反向投影结果做阈值化,得到二值图像
    if (threshold > 0.0)
        cv::threshold(result, result, 255.0 * threshold, 255.0, cv::THRESH_BINARY);

    return result;
}

最后,用 OpenCV 的 meanShift 算法追踪狒狒脸部的新位置。

cv::TermCriteria criteria(cv::TermCriteria::MAX_ITER | cv::TermCriteria::EPS,
        10,     // 最多迭代10 次
        1);     // 或者重心移动距离小于1 个像素

cv::meanShift(result, rect, criteria);

# 显示狒狒脸部区域
cv::rectangle(image, rect, cv::Scalar(0, 255, 0));
cv::namedWindow("Image 2 result");
cv::imshow("Image 2 result", image);
cv::waitKey(0);

均值偏移算法是一个迭代过程,用于定位概率函数的局部最大值,方法是寻找预定义窗口内部数据点的重心或加权平均值。然后,把窗口移动到重心的位置,并重复该过程,直到窗口中心收敛到一个稳定的点。OpenCV 实现该算法时定义了两个停止条件:迭代次数达到最大值(MAX_ITER);窗口中心的偏移值小于某个限值(EPS),可认为该位置收敛到一个稳定点。这两个条件存储在一个 cv::TermCriteria 实例中。cv::meanShift 函数返回已经执行的迭代次数。显然,结果的好坏取决于指定初始位置提供的概率分布图的质量。注意,这里用颜色直方图表示图像的外观。也可以用其他特征的直方图(例如边界方向直方图)来表示物体。

⚠️ **GitHub.com Fallback** ⚠️