由OpenCV【4】—calcHist 计算图像的直方图知道直方图能够有效地描述图像的内容。“如果一幅图像的区域中显示的是一种独特的纹理或是一个独特的物体,那么这个区域的直方图可以看作是一个概率函数,它给出的是某个像素属于该纹理或物体的概率。反投影直方图的作用是在于替换一个输入图像中每一个像素值,使其变成归一化直方图中对应的概率值。”
这个官方的翻译教程很好的解释了反投影直方图:
http://www.opencv.org.cn/opencvdoc/2.3.2/html/doc/tutorials/imgproc/histograms/back_projection/back_projection.html
测试思路:
- 截取第一张图片中的某个区域,作为感兴趣区域(待检测区域)
- 获取待检测区域的直方图,并将其直方图归一化,即得到待检测区域中的每个像素的概率图
- 利用calcBackProject函数在图像中检索。其中函数会利用待检测区域的概率图(归一化的直方图),对图像中的像素点经行映射,映射到[0,1]区间,但是所以要扩大255倍显示出图像,即缩放因子为255。
- 越亮的地方,相似度越大(得到了反投影图像后进行了二值化处理threshold)
.H文件
#ifndef COLORHISTOGRAM_H
#define COLORHISTOGRAM_H
#include <QDebug>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
using namespace cv;
class ColorHistogram
{
public:
ColorHistogram();
MatND getHistogram(const Mat& image);
Mat getHistogramImage(const Mat&image);
void colorReduce(Mat& image,int div=64);
private:
int histSize[3];
float hranges[2];
const float* ranges[3];
int channels[3];
};
#endif // COLORHISTOGRAM_H
#ifndef CONTENTFINDER_H
#define CONTENTFINDER_H
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
using namespace cv;
class ContentFinder
{
public:
ContentFinder();
// 设置直方图的阈值[0,1]
void setThreshold(float t);
// 获取阈值
float getThreshold();
// 设置参考直方图
void setHistogram(const MatND& h);
// 反投影直方图:根据指定的图像、范围及所用通道的列表
Mat find(const Mat&,float,float,int*,int);
private:
float hranges[2];
const float* ranges[3];
int channels[3];
float thresholdValue;
MatND histogram;
};
#endif // CONTENTFINDER_H
.CPP文件
#include "colorhistogram.h"
ColorHistogram::ColorHistogram()
{
//准备彩色直方图的参数
histSize[0] = histSize[1] = histSize[2] = 256;
//BGR的范围
hranges[0] = 0.0;
hranges[1] = 255.0;
//所有通道拥有相同的范围
ranges[0] = hranges;
ranges[1] = hranges;
ranges[2] = hranges;
//三个通道
channels[0] = 0;
channels[1] = 1;
channels[2] = 2;
}
MatND ColorHistogram::getHistogram(const Mat& image)
{
MatND hist;
calcHist(&image, //图像矩阵指针,可多个图像
1, //图像的数量,1张
channels, //通道的数量,一张图片(3通道的)为0~2,两张为3~5,三张为...
Mat(), //掩码图像。(不使用掩码图像)
hist, //计算返回的直方图
3, //三维直方图
histSize, //每个维度的直方图数组的大小
ranges, //像素值的范围,BGR三个通道的像素值相同
true,
false
);
return hist;
}
Mat ColorHistogram::getHistogramImage(const Mat&image)
{
//计算直方图
MatND hist = getHistogram(image);
//获取最大值和最小值
double maxValue[3] = {0};
double minValue[3] = {0};
int maxIdx[3] = {0};
int minIdx[3] = {0};
minMaxIdx(hist,minValue,maxValue,minIdx,maxIdx);
qDebug()<< hist.channels();
//显示直方图的图像
Mat histImg(histSize[0],histSize[0],CV_8U,Scalar(255));
//设置最高点为nbins的90%
int highPoint = static_cast<int>(0.9*histSize[0]);
//每个条目都绘制一条垂直线
qDebug() << hist.size;
for(int h = 10000;h < histSize[0]+10000;h++){
float binVal = hist.at<float>(h);
int intensity = static_cast<int>(binVal*highPoint/maxValue[0]);
//两点之间绘制一条直线
line(histImg,Point(h,histSize[0]),
Point(h,histSize[0]-intensity),
Scalar::all(0));
}
return histImg;
}
void ColorHistogram::colorReduce(Mat& image,int div)
{
int n1 = image.rows;
int nc = image.cols;
//图像是连续存储的吗?
if(image.isContinuous()){
//没有则对行进行填补
nc = nc*n1;
//一维数组
n1 = 1;
}
int n = static_cast<int>(log(static_cast<double>(div))/log(2.0));
//用来对像素值进行取整的二进制掩模
uchar mask = 0xFF << n;
for(int j = 0;j < n1;j++){
uchar* data = image.ptr<uchar>(j);
for(int i = 0;i < nc;i++){
*(data++) = (*data)&mask + div/2;
*(data++) = (*data)&mask + div/2;
*(data++) = (*data)&mask + div/2;
}
}
}
#include "contentfinder.h"
ContentFinder::ContentFinder():thresholdValue(-1.0f)
{
channels[0] = 0;
channels[0] = 1;
channels[0] = 2;
//所有通道的值相同。
ranges[0] = hranges;
ranges[1] = hranges;
ranges[2] = hranges;
}
// 设置直方图的阈值[0,1]
void ContentFinder::setThreshold(float t)
{
thresholdValue = t;
}
// 获取阈值
float ContentFinder::getThreshold()
{
return thresholdValue;
}
// 设置参考直方图
void ContentFinder::setHistogram(const MatND& h)
{
histogram = h;
normalize(histogram,histogram,1.0);
}
// 反投影直方图:根据直方图对指定的图像、范围及所用通道的列表进行反投影
// 即根据直方图二值化输入图像image,image的内容与直方图最相似的部分,则二值化最明显
Mat ContentFinder::find(const Mat& image,float minValue,float maxValue,int* channels,int dim)
{
Mat result;
hranges[0] = minValue;
hranges[1] = maxValue;
for(int i = 0;i < dim;i++){
this->channels[i] = channels[i];
}
calcBackProject(&image, //输入图像组
1, //一幅图像
channels, //所用通道列表
histogram, //直方图
result, //反投影的结果
ranges, //值域
255.0 //缩放因子
);
if(thresholdValue > 0.0){
threshold(result,result,255*thresholdValue,255,THRESH_BINARY);
}
return result;
}
测试:
void Widget::on_pushButton_clicked()
{
//
ColorHistogram hc;
Mat imageROI;
//读取图像
QString fileName = QFileDialog::getOpenFileName(this,
tr("Open Image"),".",
tr("Image Files(*.png *.jpg *.jpeg *.bmp)"));
if(fileName.length() <= 0)return ;
//读取彩图!!!
Mat image = imread(fileName.toUtf8().data());
imshow("image",image);
//减色
hc.colorReduce(image,32);
//绿色区域,即感兴趣区域(待检测区域)
imageROI = image(Rect(60,60,100,100));
//得到直方图
MatND hist = hc.getHistogram(imageROI);
//直方图反投影
ContentFinder finder;
//对直方图进行了归一化处理
finder.setHistogram(hist);
//设置二值化的阈值大小
finder.setThreshold(0.05f);
int channels[] = {0,1,2};
//在图片中查找感兴趣区域
Mat result = finder.find(image,0.0f,255.0f,channels,3);
//show
imshow("result",result);
}
说明:
这里是使用颜色信息来检测感与感兴趣区域相似的图像内容的。还有就是可以直接粗糙的使用灰度图像去检测。改变阈值和感兴趣区域,检测的好坏效果也不一样。