温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

openCV中如何实现meanshift算法查找目标

发布时间:2022-03-03 15:10:21 来源:亿速云 阅读:169 作者:小新 栏目:开发技术

这篇文章将为大家详细讲解有关openCV中如何实现meanshift算法查找目标,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。

    一、简介

    图像直方图的反向投影是一个概率分布图,表示一个指定图像片段出现在特定位置的概率。当我们已知图像中某个物体的大体位置时,可以通过概率分布图找到物体在另一张图像中的准确位置。我们可以设定一个初始位置,在其周围反复移动来提高局部匹配概率,从而找到物体的准确位置,这个实现过程叫做均值平移算法。

    二、实现过程

    因为人物的面部特征相对于其他位置更明显,本次实验主要应用于人物的面部识别。

    1、设定感兴趣的区域

    感兴趣区域的设定有两种方式,一种是已知图片人物脸部位置的像素坐标,通过设定矩形框来定位到人物脸部位置,另一种是使用opencv自带的selectROI函数,手动框选自己感兴趣的位置。

    openCV中如何实现meanshift算法查找目标

    2、获取脸部直方图并做归一化

    设置一个ColorHistogram类增加一个获取色调直方图的函数getHueHistogram。此函数包含将图像转换成HSV色彩空间,屏蔽低饱和度的像素(可能用到,也可能用不到),计算图像直方图。

    cv::Mat getHueHistogram(const cv::Mat &image2, int minSaturation = 0)
    	{
    		cv::Mat hist;
     
    		//转换成HSV色彩空间
    		cv::Mat hsv;
    		cv::cvtColor(image2, hsv, CV_BGR2HSV);
    		//cv::imshow("hsv", hsv);
     
    		//掩码(可能用的到也可能用不到)
    		cv::Mat mask;
    		if (minSaturation > 0) {
    			std::vector<cv::Mat>v;
    			cv::split(hsv, v);  //将3个通道分割进3幅图像
     
    			cv::threshold(v[1], mask, minSaturation, 255, cv::THRESH_BINARY);//屏蔽低饱和度的像素
    		}
     
    		//准备一维色调直方图的参数
    		hranges[0] = 0.0;
    		hranges[1] = 180.0;  //范围是0~180
    		channels[0] = 0;   //色调通道
     
    		//计算直方图
    		cv::calcHist(&hsv, 1,   //仅为一幅图像的直方图
    			channels,             //使用的通道
    			mask,                 //二值掩码
    			hist,                 //作为结果的直方图
    			1,                    //这是一维的直方图
    			histSize,             //箱子数量
    			ranges);              //像素值的范围
    		return hist;
    	}

    然后,对获取的直方图做归一化。

    void setHistogram(const cv::Mat& h) {
    		histogram = h;
    		cv::normalize(histogram, histogram, 1.0);
    	}

    3、反向投影,用meanshift查找目标

    打开第二张图像,并将其转换成HSV色彩空间(代码中对输入的图像做了resize,避免有些图像尺寸过大,显示不全),然后对第一幅图像的直方图做反向投影。下面result是反向投影的结果,目前是框选了路飞的脸部作为感兴趣区域,如果框选路飞的帽子,反向投影会有不一样的效果,大家可以自己尝试。

    //打开第二幅图像,并转换成HSV,对第一幅图像的直方图做反向投影
    	image = cv::imread("lufei2.JPG");
    	resize(image, image3, cv::Size(500, 700));
    	cv::cvtColor(image3, hsv, CV_BGR2HSV); //转换成HSV色彩空间
    	int ch[1] = { 0 };
    	cv::Mat result = finder.find(hsv, 0.0f, 180.0f, ch);

    openCV中如何实现meanshift算法查找目标

    使用openCV的meanshift算法可以将初始矩形区域修改成图像人物脸部的新位置。

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

    openCV中如何实现meanshift算法查找目标

     至此,就找到了另一张图像中人物的脸部。

    三、其他实验结果

    除了进行从单人图像找另一个单人图像的实验,还做了从单人图像找多人合影的图像,下面是对NBA球星做的一个实验。

    openCV中如何实现meanshift算法查找目标

    openCV中如何实现meanshift算法查找目标

     四、部分原理补充

    本实验为了突出感兴趣目标特征,使用了HSV色彩空间的色调分量,使用CV_BGR2HSV标志转换图像后,得到的第一个通道就是色调分量。这是一个8位分量,值范围为0~180(如果使用cv::cvtColor,转换后的图像与原始图像的类型就会是相同的)。为了提取色调图像,cv::split 函数把三通道的 HSV 图像分割成三个单通道图像。这三幅图像存放在一个 std::vector 实例中,并且色调图像是向量的第一个入口(即索引为 0)。

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

    均值偏移算法是一个迭代过程,用于定位概率函数的局部最大值,方法是寻找预定义窗口内部数据点的重心或加权平均值。然后,把窗口移动到重心的位置,并重复该过程,直到窗口中心收敛到一个稳定的点。OpenCV 实现该算法时定义了两个停止条件:迭代次数达到最大值 (MAX_ITER);窗口中心的偏移值小于某个限值(EPS),可认为该位置收敛到一个稳定点。这两个条件存储在一个 cv::TermCriteria 实例中。

    五、完整代码

    #include <iostream>
    #include<Windows.h>
    #include<opencv2/core.hpp>    //图像数据结构的核心
    #include<opencv2/highgui.hpp> //所有图形接口函数
    #include<opencv2/imgproc.hpp>
    #include <opencv2/imgproc/types_c.h>
    #include<opencv2/imgproc/imgproc.hpp>
    #include<opencv2/opencv.hpp>
     
    using namespace std;
    using namespace cv;
     
    //获得色调直方图
    class ColorHistogram
    {
    private:
    	int histSize[3]; // 每个维度的大小
    	float hranges[2]; // 值的范围(三个维度用同一个值)
    	const float* ranges[3]; // 每个维度的范围
    	int channels[3]; // 需要处理的通道
     
    public:
    	ColorHistogram() {
    		// 准备用于彩色图像的默认参数
    		// 每个维度的大小和范围是相等的
    		histSize[0] = histSize[1] = histSize[2] = 256;
    		hranges[0] = 0.0; // BGR 范围为0~256
    		hranges[1] = 256.0;
    		ranges[0] = hranges; // 这个类中
    		ranges[1] = hranges; // 所有通道的范围都相等
    		ranges[2] = hranges;
    		channels[0] = 0; // 三个通道:B
    		channels[1] = 1; // G
    		channels[2] = 2; // R
    	}
     
    	//计算一维直方图,BGR的原图转换成HSV,忽略低饱和度的像素
    	cv::Mat getHueHistogram(const cv::Mat &image2, int minSaturation = 0)
    	{
    		cv::Mat hist;
     
    		//转换成HSV色彩空间
    		cv::Mat hsv;
    		cv::cvtColor(image2, hsv, CV_BGR2HSV);
    		//cv::imshow("hsv", hsv);
     
    		//掩码(可能用的到也可能用不到)
    		cv::Mat mask;
    		if (minSaturation > 0) {
    			std::vector<cv::Mat>v;
    			cv::split(hsv, v);  //将3个通道分割进3幅图像
     
    			cv::threshold(v[1], mask, minSaturation, 255, cv::THRESH_BINARY);//屏蔽低饱和度的像素
    		}
     
    		//准备一维色调直方图的参数
    		hranges[0] = 0.0;
    		hranges[1] = 180.0;  //范围是0~180
    		channels[0] = 0;   //色调通道
     
    		//计算直方图
    		cv::calcHist(&hsv, 1,   //仅为一幅图像的直方图
    			channels,             //使用的通道
    			mask,                 //二值掩码
    			hist,                 //作为结果的直方图
    			1,                    //这是一维的直方图
    			histSize,             //箱子数量
    			ranges);              //像素值的范围
    		return hist;
    	}
     
    };
     
    class ContentFinder {
    private:
    	// 直方图参数
    	float hranges[2];
    	const float* ranges[3];
    	int channels[3];
    	float threshold; // 判断阈值
    	cv::Mat histogram; // 输入直方图
    public:
    	ContentFinder() : threshold(0.1f) {
    		// 本类中所有通道的范围相同
    		ranges[0] = hranges;
    		ranges[1] = hranges;
    		ranges[2] = hranges;
    	}
    	// 对直方图做归一化
    	void setHistogram(const cv::Mat& h) {
    		histogram = h;
    		cv::normalize(histogram, 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
    		);
    		cv::imshow("result", result);
    		return result;
    	}
    };
     
    int main()
    {
    	/************均值检测meanshift***********/
    	cv::Mat image = cv::imread("ZMS1.jpg");
    	cv::Mat image2;
    	cv::Mat image3;
    	cv::Mat hsv;
    	resize(image, image2, cv::Size(500, 700));
     
    	cv::Rect rect;
    	rect = cv::selectROI("image", image2, false, false);
    	cv::Mat imageROI = image2(rect).clone();//手动框选
     
    	/*cv::Rect rect(227, 108, 108, 104);
    	cv::Mat imageROI = image2(rect);*///手动设置矩形框选范围
     
    	cv::rectangle(image2, rect, cv::Scalar(255, 0, 0), 1, cv::LINE_8, 0);
     
    	cv::imshow("image2", image2);
    	//得到人脸直方图
    	int minsat = 65;  //最小饱和度
    	ColorHistogram hc;
    	cv::Mat colorhist = hc.getHueHistogram(imageROI, minsat);
    	
    	//把直方图传给ContentFinder类
    	ContentFinder finder;
    	finder.setHistogram(colorhist);//对直方图做归一化
     
    	//打开第二幅图像,并转换成HSV,对第一幅图像的直方图做反向投影
    	image = cv::imread("ZMS2.JPG");
    	resize(image, image3, cv::Size(500, 700));
    	cv::cvtColor(image3, hsv, CV_BGR2HSV); //转换成HSV色彩空间
    	int ch[1] = { 0 };
    	cv::Mat result = finder.find(hsv, 0.0f, 180.0f, ch);
     
    	cv::TermCriteria criteria(
    		cv::TermCriteria::MAX_ITER | cv::TermCriteria::EPS,
    		10, // 最多迭代10 次
    		1); // 或者重心移动距离小于1 个像素
    	cv::meanShift(result, rect, criteria);
    	cv::rectangle(image3, rect, cv::Scalar(0, 255, 0), 1, cv::LINE_8, 0);
    	cv::imshow("image3", image3);
    	waitKey(0);
    }

    关于“openCV中如何实现meanshift算法查找目标”这篇文章就分享到这里了,希望以上内容可以对大家有一定的帮助,使各位可以学到更多知识,如果觉得文章不错,请把它分享出去让更多的人看到。

    向AI问一下细节

    免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

    AI