计算图像直方图 - sumpig/OpenCV GitHub Wiki


直方图是一个简单的表格,表示一幅图像(有时是一组图像)中具有某个值的像素的数量。

它是后面几节应用的基础。


计算灰度图像直方图


代码实现:

cv::Mat image = cv::imread("H:\\opencv_c++\\group.jpg", 0);

Histogram1D h;
cv::Mat histo = h.getHistogram(image);
class Histogram1D {

private:

    int histSize[1];         // 直方图中箱子的数量
    float hranges[2];        // 值范围
    const float* ranges[1];  // 值范围的指针
    int channels[1];         // 要检查的通道数量

public:

    Histogram1D() {

        // 准备一维直方图的默认参数
        histSize[0] = 256;   // 256 个箱子
        hranges[0] = 0.0;    // 从0 开始(含)
        hranges[1] = 256.0;  // 到256(不含)
        ranges[0] = hranges;
        channels[0] = 0;     // 先关注通道0
    }

    // 计算一维直方图
    cv::Mat getHistogram(const cv::Mat& image) {

        cv::Mat hist;
        // 用calcHist 函数计算一维直方图
        cv::calcHist(&image,
            1,          // 仅为一幅图像的直方图
            channels,   // 使用的通道
            cv::Mat(),  // 不使用掩码
            hist,       // 作为结果的直方图
            1,          // 这是一维的直方图
            histSize,   // 箱子数量
            ranges      // 像素值的范围
        );

        return hist;
    }

打印直方图数值:

// 打印直方图箱子数值
for (int i = 0; i < 256; i++)
	std::cout << "Value" << i << " = " << histo.at<float>(i) << std::endl;

显示直方图柱状图:

// 以图像形式显示直方图
cv::namedWindow("Histogram");
cv::imshow("Histogram", h.getHistogramImage(image));
// 计算一维直方图,并返回它的图像
cv::Mat getHistogramImage(const cv::Mat &image, int zoom=1) {

    // 先计算直方图
    cv::Mat hist= getHistogram(image);
    // 创建图像
    return getImageOfHistogram(hist, zoom);
}

// 创建一个表示直方图的图像(静态方法)
static cv::Mat getImageOfHistogram(const cv::Mat &hist, int zoom) {

    // 取得箱子值的最大值和最小值
    double maxVal = 0;
    double minVal = 0;
    cv::minMaxLoc(hist, &minVal, &maxVal, 0, 0);

    // 取得直方图的大小
    int histSize = hist.rows;
    // 用于显示直方图的方形图像
    cv::Mat histImg(histSize*zoom, histSize*zoom, CV_8U, cv::Scalar(255));

    // 设置最高点为90%(即图像高度)的箱子个数
    int hpt = static_cast<int>(0.9*histSize);

    // 为每个箱子画垂直线
    for (int h = 0; h < histSize; h++) {

        float binVal = hist.at<float>(h);
        if (binVal > 0) {

            int intensity = static_cast<int>(binVal*hpt / maxVal);
            cv::line(histImg, 
                    cv::Point(h*zoom, histSize*zoom),    // 直线起点
                    cv::Point(h*zoom, (histSize - intensity)*zoom), // 直线终点
                    cv::Scalar(0), // 线条颜色
                    zoom);  // 线条粗细
        }
    }

    return histImg;
}

计算彩色图像的直方图

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
    }

    // 计算直方图
    cv::Mat getHistogram(const cv::Mat &image) {

        cv::Mat hist;
        // 计算直方图
        cv::calcHist(&image, 1, // 单幅图像的直方图
                     channels, // 用到的通道
                     cv::Mat(), // 不使用掩码
                     hist, // 得到的直方图
                     3, // 这是一个三维直方图
                     histSize, // 箱子数量
                     ranges);// 像素值的范围
                     
        return hist;
    }

如果选用含有256 个箱子的直方图,这个矩阵就有(256)^3 个元素,表示超过1600 万个项目。在很多应用程序中,最好在计算直方图时减少箱子的数量。

也可以使用数据结构cv::SparseMat 表示大型稀疏矩阵(即非零元素非常稀少的矩阵),这样不会消耗过多的内存。

// 计算直方图
cv::SparseMat getSparseHistogram(const cv::Mat &image) {

    cv::SparseMat hist(3, // 维数
                       histSize, // 每个维度的大小
                       CV_32F);

    // 计算直方图
    cv::calcHist(&image, 1, // 单幅图像的直方图
                 channels, // 用到的通道
                 cv::Mat(), // 不使用掩码
                 hist, // 得到的直方图
                 3, // 这是三维直方图
                 histSize, // 箱子数量
                 ranges); // 像素值的范围
    
    return hist;
}

扩展

灰度直方图尖峰往往分割了图像的背景和前景,利用阈值化处理可得到二值分割图像。

cv::Mat thresholded; // 输出二值图像
cv::threshold(image,
             thresholded,
             70, // 阈值
             255, // 对超过阈值的像素赋值
             cv::THRESH_BINARY); // 阈值化类型
⚠️ **GitHub.com Fallback** ⚠️