直方图反向投射完全讲解
https://blog.csdn.net/shuiyixin/article/details/80331839
反向投影作用——目标检测
一幅图像的反向投影利用了其原始图像(或目标区域)的直方图,
将该直方图作为一张查找表来找对应像素点的像素值,即将目标
图像像素点的值设置为原始图像(或目标区域)直方图上对应的bin值
该bin值代表了(目标区域)上该像素值出现的概率 从而得到一幅图像的概率值
从而我们可以通过这幅概率图可以得知在这幅图像中,目标出现可能出现的位置
反向投影(Back Projection)
反向投影是反映直方图在目标图像中的分布情况
简单点说就是用直方图模型去目标图像中寻找是存在否相似的对象。
通常用HSV色彩空间的HS两个通道直方图模型
具体操作是先得到一张图像的直方图信息,然后遍历原图像的每一个像素,
如果这个像素值在直方图的某个bin下,就用这个bin出现的频次去代替这个
像素值。这样划分了多少个bin,新生成的图像就有多少个像素值种类(而不
是原来的0-255的256个种类)。比如说将0-256分成16份(16个bin),计算
直方图得到原图像(0,0)位置处的像素值为250,落在了第16个bin下,而
原图像直方图在这个bin下的值(频次)为56(经过归一化),则反向投影图像中
(0,0)位置处的像素值为56。
应用场景:从某张图像中找出相似对象。假如拍摄的两张图片都含有同一目标物体
(如两只手的图像)。可以用其中一张图像提取出的直方图信息,去遍历另一张图
片的像素,若遍历到目标像素值,则它可以被替换为第一张图片出现该像素值的个数
,如果这张图片中含有相同目标,会被显示出来。
例如用第一张图片的直方图信息去遍历第二张图片生成反投影信息:(左侧为原图,右侧为测试图)
2、步骤
1.建立直方图模型
2.计算待测图像直方图并映射到模型中
3.从模型反向计算生成图像
详细理解原理可参考这篇博客https://blog.csdn.net/michaelhan3/article/details/73550643
代码实现步骤
1.加载图片 imread
2.将图像从RGB空间转换到HSV色彩空间cvtColor
因为BGR直方图我们只能三个通道分别计算,计算后也没法合在一起,也就是不能把图片作
为一个整体来比较,而H-S就可以将图片作一个整体对比,所以这里用H-S直方图来作对比。
3.计算直方图(统计各像素值(或像素区间)在原图的总数量)并归一化calcHist与normlize
4.Mat与MatND,其中Mat表示二维数组,MatND表示三维或者多维数组,此处均可以用Mat表示
HSV 表达彩色图像的方式由三个部分组成:
Hue(色调、色相)
Saturation(饱和度、色彩纯净度)
Value(明度)
#include
#include
#include
#include
using namespace cv;
using namespace std;
Mat src, dst,hue,hsv,input,goal,hue2,hsv2,mix,ROIimg;
int bins = 12;
void HistBackPro(int,void*);
void Mixchannels();
void Histsearch(int, void*);
int main()
{
src = imread("D:/实验台/机器视觉/测试图片/手势识别.jpg");
input = imread("D:/实验台/机器视觉/测试图片/球.jpg");
goal = imread("D:/实验台/机器视觉/测试图片/球局部.jpg");
ROIimg=imread("D:/实验台/机器视觉/测试图片/感兴趣区域检测结果.jpg");
if (src.empty())//如果src这个数据库属性为空
{
cout << "无法打开" << endl;
return -1;
}
//imshow("原图", src);
//namedWindow("直方图反向投影", WINDOW_AUTOSIZE);
//HistBackPro(0,0);
//createTrackbar("Hist Hue Bins", "直方图反向投影", &bins, 180, HistBackPro);
//Mixchannels();
Histsearch(0, 0);
createTrackbar("Hist Hue Bins", "直方图反向投影区域查找", &bins, 180, Histsearch);
waitKey(0);
return 0;
}
void HistBackPro(int, void*)
{
//先获取hsv的S(饱和度)通道(HS两通道是配合使用的缺一不可 但我们进行直方图计算的时候只需要计算H通道)
//为什么要把BGR转换为HSV空间 因为这个案例我们需要分割图片 需要分割颜色区域 需要使用颜色直方图进行分割 BGR没有颜色通道 HSV的H为颜色通道
cvtColor(src, hsv, CV_BGR2HSV);
imshow("BGR色彩空间src转换为HSV色彩空间hsv", hsv);
hue.create(hsv.size(), hsv.depth());//分配给hue(色调) 与 hsv图像hsv一致的 大小和深度(S) 便于后期的通道拷贝
imshow("hue获取hsv的S色深通道", hue);
//再获取hsv的H(色调)通道
int from_to[] = { 0,0 };//序号对向量的传输需要传输地址 于是就用数组表示(数组名就是数组的地址)
//{0,0}表示把hsv的第一个通道(色调)拷贝给hue的第一个通道(色调) 现在hue就有了hsv的色调(H)和深度饱和度(S)
//以此类推 int from_to[] = { 0, 2, 1, 1, 2, 0, 2, 2 }; size_t npairs -fromTo中的序号对数 等于 4 就表示
//把hsv1通道给hue3通道 把hsv2通道给hue2通道 把hsv3通道给hue1通道 把hsv3通道给hue3通道
//mixChannelsAPI详解https://www.pianshen.com/article/4148149685/
mixChannels(&hsv, 1, &hue, 1, from_to, 1);// 从输入中拷贝某通道到输出中特定的通道
imshow("hue获取hsv的H色调通道", hue);
/*void mixChannels(
const Mat* src- 被拷贝的通道的来源的地址,一系列输入图像的数组, 被拷贝的通道的来源
size_t nsrcs-输入矩阵的个数
Mat* dst-输出矩阵的地址(事先分配空间大小深度 需和输入矩阵相同)
size_t ndsts-输出矩阵的个数
const int* fromTo-序号对向量的地址 (决定哪个通道被拷贝 拷贝到那个通道 此案例为HS通道)
size_t npairs -fromTo中的序号对数(两个算1对)
)*/
//mixChannels主要就是把输入的矩阵(或矩阵数组)(hsv)的某些通道拆分复制给对应
//的输出矩阵(或矩阵数组)(hue)的某些通道中,其中的对应关系就由fromTo参数(nchannels)指定.
//hue获取了hsv的色调(H)深度(S)之后
//计算直方图(此案例中直方图为一维度 Hue的范围0-180度 bin范围(直方维度)设为可选择)
float range[] = { 0,180 };//Hue的在opencv中的色调范围
//bins 由滑动条调节 直方图区间
const float* histRanges = { range };//直方图bin的计算范围区间
Mat Hhist;//Hue的H通道直方图数据容器 ##使用H(色调)颜色直方图进行计算 因为物体的颜色信息比灰度图像更容易被分割和识别
calcHist(&hue, 1, 0, Mat(), Hhist, 1, &bins, &histRanges, true, false);
//输入的源图像的地址(可为多个图像)
//输入的源图像的数目
//维度通道序列 此案例为 hue的(0 H 色深) 通道
//掩膜操作 选取需要操作的区域
//直方图dims 维度(通道数)
//在直方图维度上直方的个数 的地址 (直方图的区间)
//一维二元数组组成的直方图需要统计的(0-180)区间范围(只统计值为0-180的数据)
//均匀化直方图 是直方图区间在 0-180均匀分布
//累计标识
//直方图数据归一化操作
normalize(Hhist, Hhist, 0, 255, NORM_MINMAX, -1, Mat()); //归一化操作 无指定范围时默认为0-1
//出现的频次应在0-255之间,因为要用这个频次做为反投影图像的像素值
//直方图反向投影
Mat histback;//反射投影目标模板
calcBackProject(&hue, 1, 0, Hhist, histback, &histRanges, 1, true);
//const Mat* images:输入图像(直方信息矩阵),图像深度必须位CV_8U, CV_16U或CV_32F中的一种,尺寸相同,每一幅图像都可以有任意的通道数
//int nimages : 输入图像的数量
//const int* channels : 用于计算反向投影的通道列表,通道数必须与直方图维度相匹配
//InputArray hist : 需要查找的图像或(直方信息矩阵),直方图的bin可以是密集(dense)或稀疏(sparse)
// OutputArray backProject : 目标反向投影输出图像,是一个单通道图像,与原图像有相同的尺寸和深度
//const float ranges** : 直方图中的区间取值范围
//double scale = 1 : 可选输出反向投影的比例因子(默认为1)
//bool uniform = true : 直方图是否均匀分布(uniform)的标识符,有默认值true
//##注意!模板矩阵和查找矩阵 通道数 色彩类型必须一致!
imshow("直方图反向投影", histback);
//绘制直方图
int histH = 400;//直方图画布的高度
int histW = 400;//宽度
Mat Histimg(histH, histW, CV_8UC3, Scalar(0, 0, 0));
int binW = (histW / bins);//直方图bin的宽度
for (int i = 1; i < bins; i++)
{
rectangle(Histimg,
Point((i - 1) * binW, (histH - cvRound(Hhist.at<float>(i - 1) * (400/255)))),
//矩形框的第一个点,直方图的左上角顶点。因为之前将其归一化到0-255之间,而整张图片的高度为400,所以y的值最后要在图像的尺度下表示
Point(i * binW, histH), Scalar(0, 0, 255),-1,8);//矩形框的第二个点,直方图的右下角顶点
//如果线宽为-1表示填充矩形
//画矩形(目标图像,(左上顶点X坐标,左上顶点Y坐标),(右下顶点X坐标,右下顶点Y坐标),颜色设置,线宽设置,线型设置;
}
imshow("直方图绘制",Histimg);
return;
}
//mixchannels通道分离试验
void Mixchannels()
{
Mat bgra(500, 500, CV_8UC3, Scalar(255, 255, 0));
//注意色序 Scalar(B,G,R);
imshow("bgra原图", bgra);
Mat bgr(bgra.rows, bgra.cols, CV_8UC3);
Mat alpha(bgra.rows, bgra.cols, CV_8UC1);
Mat out[] = { bgr, alpha };
int from_to[] = { 0, 2, 1, 1, 2, 0 };
mixChannels(&bgra, 1, &bgr, 1, from_to, 3);
imshow("bgra", bgra);
imshow("bgr", bgr);
waitKey(0);
}
实战 利用直方图反射进行直方图标定区域查找显示 ROI(region of interest),感兴趣区域
void Histsearch(int,void*)
{
//转换模板图片‘球’为hsv空间
cvtColor(input, hsv, CV_BGR2HSV);
imshow("hsv颜色空间", hsv);
//取球这幅图的S空间并转移数据到hue这个模板
hue.create(hsv.size(), hsv.depth());
int from_to[] = { 0,0 };
mixChannels(&hsv, 1, &hue, 1, from_to, 1);//取球这幅图的H空间
imshow("HS通道处理后",hue);
//转换需要查找的区域图片‘球局部’为hsv空间
cvtColor(goal, hsv2,CV_BGR2HSV);
hue2.create(hsv2.size(), hsv2.depth());//取球这幅图的S空间并转移数据到hue这个模板
int from_to2[] = { 0,0 };
mixChannels(&hsv2, 1, &hue2, 1, from_to2, 1);//取球这幅图的H空间
float range2[] = { 0,180 };//Hue2的在opencv中的色调范围
//bins 由滑动条调节 直方图区间
const float* histRanges2 = { range2 };//直方图bin的计算范围区间
//计算球局部的直方数据
Mat Hhist2;
calcHist(&hue2, 1, 0, Mat(), Hhist2, 1, &bins, &histRanges2, true, false);
normalize(Hhist2, Hhist2, 0, 255, NORM_MINMAX, -1, Mat());
//在hue图中寻找符合‘球局部’的直方数据并投射显示
Mat HistBackROI;//反射投影目标模板
calcBackProject(&hue, 1, 0, Hhist2, HistBackROI, &histRanges2, 1, true);
imshow("直方图反向投影区域查找", HistBackROI);
imwrite("D:/实验台/机器视觉/测试图片/感兴趣区域检测结果.jpg", HistBackROI);
bitwise_and(input, ROIimg,dst);
//按位与操作(蒙版输入源1,蒙版输入源2,蒙版结果目标矩阵)
//两幅蒙版输入源图片的通道数 颜色类型和图片大小需要一致
//原理 某一像素值不为0(白色)就会被复制到目标矩阵
imshow("最终结果",dst);
//绘制直方图
int histH = 400;//直方图画布的高度
int histW = 400;//宽度
Mat Histimg(histH, histW, CV_8UC3, Scalar(0, 0, 0));
int binW = (histW / bins);//直方图bin的宽度
for (int i = 1; i < bins; i++)
{
rectangle(Histimg,
Point((i - 1) * binW, (histH - cvRound(Hhist2.at<float>(i - 1) * (400 / 255)))),
Point(i * binW, histH), Scalar(0, 0, 255), 1, 8);
}
imshow("直方图绘制", Histimg);
return;
}
//项目来源 https://blog.csdn.net/tengfei461807914/article/details/77075567?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-12.nonecase&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-12.nonecase
原理及其API详解
https://www.cnblogs.com/bjxqmy/p/12452420.html
直方图反向投射API
calcBackProject
(
const Mat* images,//输入图像的指针,可以传入多张图像
int nimages,//输入图像的数量
const int* channels,//用于计算反向投影的通道列表,通道数必须与直方图维度相匹配
InputArray hist,//输入的直方图
OutputArray backProject,//目标反向投影输出图像,是一个单通道图像,与原图像有相同的尺寸和深度
const float ranges**,//直方图中每个维度bin的取值范围
double scale=1,//可选输出反向投影的比例因子
bool uniform=true//直方图是否均匀分布(uniform)的标识符,有默认值true
)
反向投影的工作原理
反向投影图中,某一位置(x,y)的像素值 = 原图对应位置(x,y)
像素值在原图的总数目。 即若原图中(5,5)位置上像素值为 200,
而原图中像素值为 200 的像素点有 500 个,则反向投影图中(5,5)
位置上的像素值就设为 500
反向投影可以用来做图像分割,寻找感兴趣区间。它会输出与输入图像大小相同的图像,
每一个像素值代表了输入图像上对应点属于目标对象的概率,简言之,输出图像中像素
值越高的点越可能代表想要查找的目标。直方图投影经常与camshift(追踪算法)算法
一起使用
算法实现的方法,首先要为包含我们感兴趣区域的图像建立直方图(样例要找一片草坪
,其他的不要)。被查找的对象最好是占据整个图像(图像里全是草坪)。最好使用颜
色直方图,物体的颜色信息比灰度图像更容易被分割和识别。再将颜色直方图投影到输
入图像查找目标,也就是找到输入图像中每一个像素点的像素值在直方图中对应的概率
,这样就得到一个概率图像,最后设置适当的阈值对概率图像进行二值化