【从零学习openCV】使用直方图统计像素

1. 计算图像直方图

图像是由像素组成的,在一个单通道的灰度图像中,每个像素的值介于0到255之间,而直方图就是一个简单的表,给出了一幅或者一组图像中拥有给定数值的像素数量。当然直方图也可以归一化,归一化后的所有项的和为1,在这种情况下,每一项给出的都是拥有特定数值的像素在图像中占的比例。

下面我们看看如何用opencv计算单通道图像的直方图,我用一个Histogram1D封装了与单通道直方图操作相关的变量和函数:

#ifndef HISTOGRAM1D_H
#define HISTOGRAM1D_H

#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>

using namespace cv;

class Histogram1D{
private:
    int histSize[1];       //项的数量
    float hranges[2];   //像素的最小及最大值
    const float* ranges[1];
    int channels[1];    //仅用到一个通道
public:
    Histogram1D(){
        //准备1D直方图的参数
        histSize[0] =256;
        hranges[0] = 0.0;
        hranges[1] = 255.0;
        ranges[0]  = hranges;
        channels[0] = 0;  //默认情况,我们考察0号通道
    }

    //计算1D直方图
    MatND getHistogram(const Mat&image)
    {
        MatND  hist;
        //计算直方图
        calcHist(&image,
                 1,                     //计算单张图像的直方图
                 channels,        //通道的数量
                 Mat(),              //不使用图像作为掩码
                 hist,                //返回的直方图
                 1,                    //这是1D的直方图
                 histSize,         //项的数量
                 ranges            //像素值的范围
                 );
        return hist;
    }

    //计算1D直方图,并返回一幅图像
    Mat getHistogramImage(const Mat &image)
    {
        //首先计算直方图
        MatND hist = getHistogram(image);
        //获取最大值和最小值
        double maxVal = 0;
        double minVal = 0;
        minMaxLoc(hist,&minVal,&maxVal,0,0);
        //显示直方图的图像
        Mat histImg(histSize[0],histSize[0],CV_8U,Scalar(255));
        //设置最高点为nbins的90%
        int hpt  = static_cast<int>(0.9*histSize[0]);
        //每个条目都绘制一条垂直线
        for(int h = 0;h<histSize[0];h++){
            float binVal =hist.at<float>(h);
            int intensity = static_cast<int>(binVal*hpt/maxVal);
            //两点之间绘制一条线
            line(histImg,Point(h,histSize[0]),
                    Point(h,histSize[0]-intensity),
                    Scalar::all(0));
        }
        return histImg;
    }
};
#endif // HISTOGRAM1D_H
main函数如下,直接调用getHistogramImage即可得到直方图:

#include <QCoreApplication>
#include "Histogram1D.h"

int main(int argc, char *argv[])
{
    namedWindow( "src1", WINDOW_AUTOSIZE );
    namedWindow( "src2", WINDOW_AUTOSIZE );
    Mat image = imread( "test.jpg",0 );  //读取灰度图
    Histogram1D h;
    while(1)
    {
        imshow( "src1", image);
        imshow( "src2", h.getHistogramImage(image));
        char c =  waitKey(30);
        if( 27==c )
            return 0;
    }
}
效果如下:

原始灰度图像


直方图


当然我们还可以用calcHist函数来计算彩色BGR图像的直方图,这时候直方图变成三维的了:

#ifndef COLORHISTOGRAM_H
#define COLORHISTOGRAM_H
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include<QDebug>

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;
        hranges[1] = 255.0;
        ranges[0] =ranges[1]=ranges[2]=hranges;       //所有通道都有相同的范围
        channels[0] = 0;  //3个通道
        channels[1] = 1;
        channels[2] = 2;
    }

    //计算三维直方图
    MatND getHistogram(const Mat&image)
    {
        MatND  hist;
        //计算直方图
        calcHist(&image,
                 1,                     //计算单张图像的直方图
                 channels,        //通道的数量
                 Mat(),              //不使用图像作为掩码
                 hist,                //返回的直方图
                 3,                    //这是三维的直方图
                 histSize,         //项的数量
                 ranges            //像素值的范围
                 );
        qDebug()<<hist.dims;
        return hist;
    }
};

#endif // COLORHISTOGRAM_H
这时候getHistogram返回的是一个256*256*256的矩阵,包含了超过1600万个元素,这计算代价是非常大的,可以参考opencv操作像素中所述的方法减少颜色的数量,或者可以使用cv::SparseMat数据结构,用于存储大型的稀疏矩阵,这样可以极大地减少内存的消耗。

PS:至于绘制三维直方图的绘制比较麻烦,以后有时间看看用qt实现个界面,感兴趣的话可以先参看下这篇文章http://blog.csdn.net/foolpanda1168/article/details/6078463

2. 查找表的使用

查找表实际上就是一个简单的一对一(或者多对一)的函数,定义了如何将像素值转换为新的值,本质上就是一个一维数组。

openCV中用cv::LUT的方法对图像应用查找表生成新图像,我们将这个功能加到Histogram1D类中:

//应用查找表,image为输入图像,lookup是1*256的unchar矩阵,代表查找表
Mat applyLookUp(const Mat&image,const Mat&lookup)
{
    Mat result;
    //应用查找表
    LUT(image,lookup,result);
    return result;
}
这时我们可以做个简单的实验,比如将原先每个像素强度进行反转,即x变成255-x:

    int dim(256);
    Mat lut(1,&dim,CV_8U);
    for(int i=0;i<256;i++)
    {
        lut.at<uchar>(i) = 255-i;
    }
    Mat reverse = h.applyLookUp(image,lut);

效果如下:



3. 直方图均衡化

直方图均衡化其实就是为了让直方图更加的平坦,能够大幅改善图像的外观。

openCV中提供了一个简单易用的函数cv::equalizeHist来执行直方图均衡化。

//直方图均衡化
    Mat  equalize(const Mat &image)
    {
        Mat result;
        equalizeHist(image,result);
        return result;
    }

应用与之前的图像,效果如下:



可以看出图像的对比度增强了,直方图也更加的平坦。


参考书籍
《openCV2计算机视觉编程手册》

(转载请注明作者和出处:Shawn-HT  http://blog.csdn.net/shawn_ht



  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值