直方图

一、 绘制直方图

1.1 代码

#include <iostream>
#include "pch.h"
#include <stdio.h>
#include <opencv2/opencv.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>

using namespace std;
using namespace cv;

/*  Histogram 直方图 */
void getHistogram(Mat& img) {
    // 分割通道
    vector<Mat> rgb_planes;
    split(img, rgb_planes);

    int histSize = 255;

    float range[] = {0, 255};
    const float* histRange = { range };

    bool uniform = true;
    bool accumulate = false;

    Mat r_hist, g_hist, b_hist;

    // 计算直方图
    calcHist(&rgb_planes[0], 1, 0, Mat(), r_hist, 1, &histSize, &histRange, uniform, accumulate);
    calcHist(&rgb_planes[1], 1, 0, Mat(), g_hist, 1, &histSize, &histRange, uniform, accumulate);
    calcHist(&rgb_planes[2], 1, 0, Mat(), b_hist, 1, &histSize, &histRange, uniform, accumulate);

    // 创建直方图画布
    int hist_w = 600;
    int hist_h = 600;
    int bin_w = cvRound((double)hist_w / histSize);

    Mat histImage(hist_w, hist_h, CV_8UC3, Scalar(255, 255, 255));

    // 归一化
    normalize(r_hist, r_hist, 0, histImage.rows, NORM_MINMAX, -1, Mat());
    normalize(g_hist, g_hist, 0, histImage.rows, NORM_MINMAX, -1, Mat());
    normalize(b_hist, b_hist, 0, histImage.rows, NORM_MINMAX, -1, Mat());
       
    // 绘制直方图
    for (int i = 1; i < histSize; i++) {
        line(histImage, 
            Point(bin_w*(i - 1), hist_h - cvRound(r_hist.at<float>(i - 1))),
            Point(bin_w*i, hist_h - cvRound(r_hist.at<float>(i))),
            Scalar(255, 0, 0), 1, 8, 0);
        line(histImage,
            Point(bin_w*(i - 1), hist_h - cvRound(g_hist.at<float>(i - 1))),
            Point(bin_w*i, hist_h - cvRound(g_hist.at<float>(i))),
            Scalar(0, 255, 0), 1, 8, 0);
        line(histImage,
            Point(bin_w*(i - 1), hist_h - cvRound(b_hist.at<float>(i - 1))),
            Point(bin_w*i, hist_h - cvRound(b_hist.at<float>(i))),
            Scalar(0, 0, 255), 1, 8, 0);
    }

    namedWindow("Histogram", WINDOW_AUTOSIZE);
    imshow("Histogram", histImage);
}

int main()
{
    Mat src;

    src = imread("input.jpg");

    if (!src.data)
        return -1;

    getHistogram(src);

    waitKey();
    return 0;
}

1.2 效果

二、全局直方图均衡

2.1 代码

#include <iostream>
#include "pch.h"
#include <stdio.h>
#include <opencv2/opencv.hpp>
#include <opencv2/imgproc/types_c.h>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>

using namespace std;
using namespace cv;

/* Histogram 直方图 */
void getHistogram(Mat& img, String str) {
    Mat dst(img.rows, img.cols, CV_8UC3, Scalar(0));
    img.copyTo(dst);
    // 分割通道
    vector<Mat> rgb_planes;
    split(dst, rgb_planes);

    int histSize = 255;

    float range[] = { 0, 255 };
    const float* histRange = { range };

    bool uniform = true;
    bool accumulate = false;

    Mat r_hist, g_hist, b_hist;

    // 计算直方图
    calcHist(&rgb_planes[0], 1, 0, Mat(), r_hist, 1, &histSize, &histRange, uniform, accumulate);
    calcHist(&rgb_planes[1], 1, 0, Mat(), g_hist, 1, &histSize, &histRange, uniform, accumulate);
    calcHist(&rgb_planes[2], 1, 0, Mat(), b_hist, 1, &histSize, &histRange, uniform, accumulate);

    // 创建直方图画布
    int hist_w = 500;
    int hist_h = 500;
    int bin_w = cvRound((double)hist_w / histSize);

    Mat histImage(hist_w, hist_h, CV_8UC3, Scalar(255, 255, 255));

    // 归一化
    normalize(r_hist, r_hist, 0, histImage.rows, NORM_MINMAX, -1, Mat());
    normalize(g_hist, g_hist, 0, histImage.rows, NORM_MINMAX, -1, Mat());
    normalize(b_hist, b_hist, 0, histImage.rows, NORM_MINMAX, -1, Mat());

    // 绘制直方图
    for (int i = 1; i < histSize; i++) {
        line(histImage,
            Point(bin_w*(i - 1), hist_h - cvRound(r_hist.at<float>(i - 1))),
            Point(bin_w*i, hist_h - cvRound(r_hist.at<float>(i))),
            Scalar(255, 0, 0), 1, 8, 0);
        line(histImage,
            Point(bin_w*(i - 1), hist_h - cvRound(g_hist.at<float>(i - 1))),
            Point(bin_w*i, hist_h - cvRound(g_hist.at<float>(i))),
            Scalar(0, 255, 0), 1, 8, 0);
        line(histImage,
            Point(bin_w*(i - 1), hist_h - cvRound(b_hist.at<float>(i - 1))),
            Point(bin_w*i, hist_h - cvRound(b_hist.at<float>(i))),
            Scalar(0, 0, 255), 1, 8, 0);
    }

    namedWindow(str, WINDOW_AUTOSIZE);
    imshow(str, histImage);
}

/* 直方图均衡 Histogram Equalization*/
Mat equalizeHist_Gray(Mat& img) {
    Mat dst(img.rows, img.cols, CV_8UC1, Scalar(0));
    img.copyTo(dst);
    if (dst.type() == CV_8UC3)
        cvtColor(dst, dst, CV_BGR2GRAY);

    // 计算灰度值
    double grayScale[256] = { 0 };
    for (int i = 0; i < dst.rows; i++)
        for (int j = 0; j < dst.cols; j++)
            grayScale[dst.at<uchar>(i, j)]++;

    // 计算灰度分布密度
    int pixels = dst.rows*dst.cols;
    double density[256] = { 0 };

    for (int i = 0; i < 256; i++)
        density[i] = grayScale[i] / (double)pixels;

    // 计算累计直方图分布
    double temp[256] = { 0 };

    temp[0] = density[0];

    for (int i = 1; i < 256; i++)
        temp[i] = temp[i - 1] + density[i];

    // 累计分布取整
    int result[256] = { 0 };

    for (int i = 0; i < 256; i++)
        result[i] = (int)(255 * temp[i] + 0.5);

    // 灰度值映射
    for (int i = 0; i < dst.rows; i++)
        for (int j = 0; j < dst.cols; j++)
            dst.at<uchar>(i, j) = result[dst.at<uchar>(i, j)];

    return dst;
}

Mat equalizeHist_RGB(Mat& img) {
    Mat temp(img.rows, img.cols, CV_8UC3, Scalar(0));
    img.copyTo(temp);
    if (temp.type() == CV_8UC1)
        cvtColor(temp, temp, CV_GRAY2RGB);

    Mat matArray[3];
    split(temp, matArray);
    for (int i = 0; i < 3; i++)
        matArray[i] = equalizeHist_Gray(matArray[i]);
    Mat dst;
    merge(matArray, 3, dst);
    return dst;
}

/* PSNR 峰值信噪比 */
double getPSNR(Mat& src, Mat& img) {
    Mat dst;
    
    // 矩阵对应元素作差绝对值
    absdiff(src, img, dst);
    dst.convertTo(dst, CV_32F);

    // 计算矩阵对应元素差的平方
    dst.mul(dst);
    
    Scalar s = sum(dst);
    double sse = s.val[0] + s.val[1] + s.val[2];

    if (sse <= 1e-10)
        return 0;
    else {
        double mse = sse / double(src.channels()*src.total());
        double psnr = 10.0*log10(255 * 255 / mse);
        return psnr;
    }
}

/* SSIM 结构相似性 */
Scalar getSSIM(const Mat& src, const Mat& img) {
    const double k1 = 0.01, k2 = 0.03, L = 255;
    double c1 = (k1*L)*(k1*L), c2 = (k2*L)*(k2*L);

    Mat I1, I2;
    src.convertTo(I1, CV_32F);
    img.convertTo(I2, CV_32F);
    Mat I1_2 = I1.mul(I1);
    Mat I2_2 = I2.mul(I2);
    Mat I1_I2 = I1.mul(I2);

    Mat u1, u2;
    GaussianBlur(I1, u1, Size(11, 11), 1.5);
    GaussianBlur(I2, u2, Size(11, 11), 1.5);

    Mat u1_2 = u1.mul(u1);
    Mat u2_2 = u2.mul(u2);
    Mat u1_u2 = u1.mul(u2);

    Mat sigma1_2, sigma2_2, sigma1_sigma2;
    GaussianBlur(I1_2, sigma1_2, Size(11, 11), 1.5);
    sigma1_2 -= u1_2;
    GaussianBlur(I2_2, sigma2_2, Size(11, 11), 1.5);
    sigma2_2 -= u2_2;
    GaussianBlur(I1_I2, sigma1_sigma2, Size(11, 11), 1.5);
    sigma1_sigma2 -= u1_u2;

    Mat t1, t2, t3;
    t1 = 2 * u1_u2 + c1;
    t2 = 2 * sigma1_sigma2 + c2;
    t3 = t1.mul(t2);

    t1 = u1_2 + u2_2 + c1;
    t2 = sigma1_2 + sigma2_2 + c2;
    t1 = t1.mul(t2);

    Mat ssim_map;
    divide(t3, t1, ssim_map);
    Scalar ssim = mean(ssim_map);
    return ssim;
}

/* 调用OpenCV直方图均衡函数 */
Mat equalizeHist_OpenCVGray(Mat& matSrc) {
    Mat matArray;
    cvtColor(matSrc, matSrc, CV_BGR2GRAY);

    Mat dst;
    equalizeHist(matSrc, dst);
    return dst;
}

Mat equalizeHist_OpenCVRGB(Mat& matSrc)
{
    Mat matArray[3];
    split(matSrc, matArray);
    // 直方图均衡化
    for (int i = 0; i < 3; i++)
        equalizeHist(matArray[i], matArray[i]);
    Mat dst;
    merge(matArray, 3, dst);
    return dst;
}

int main()
{
    Mat src, imgGray, imgRGB, imgCheck;

    src = imread("./TestImages/car.jpg");
    if (!src.data)
        return -1;

    imgGray = equalizeHist_Gray(src);
    imgRGB = equalizeHist_RGB(src);
    imgCheck = equalizeHist_OpenCVRGB(src);

    imshow("Raw", src);
    imshow("Equalized_Gray", imgGray);
    imshow("Equalized_RGB", imgRGB);

    cout << "(RAW/Equalized)PSNR:" << getPSNR(src, imgRGB) << "dB" << endl;
    cout << "(RAW/EqualizedCV)PSNR:" << getPSNR(src, imgCheck) << "dB" << endl;
    cout << "(OpenCV/Mine)PSNR:" << getPSNR(imgCheck, imgRGB) << "dB\n" << endl;

    cout << "(RAW/Equalized)SSIM:" << getSSIM(src, imgRGB) << endl;
    cout << "(RAW/EqualizedCV)SSIM:" << getSSIM(src, imgCheck) << endl;
    cout << "(OpenCV/Mine)SSIM:" << getSSIM(imgCheck, imgRGB) << endl;
    
    getHistogram(src, "RawHistogram");
    getHistogram(imgRGB, "EqualizedHistogram");

    waitKey();
    return 0;
}

1.2 效果

Sample1 暗图像均衡

Sample1 直方图对比

Sample2 亮图像均衡

Sample2 直方图对比

Sample3 低对比度均衡

Sample3 直方图对比

Sample4 高对比度均衡

Sample4 直方图对比
原图-灰度图均衡-RGB均衡

直方图对比

峰值信噪比与结构相似性

三、直方图匹配

3.1 代码

#include <iostream>
#include "pch.h"
#include <stdio.h>
#include <opencv2/opencv.hpp>
#include <opencv2/imgproc/types_c.h>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>

using namespace std;
using namespace cv;

/* Histogram 直方图 */
void getHistogram(Mat& img, String str) {
    Mat dst(img.rows, img.cols, CV_8UC3, Scalar(0));
    img.copyTo(dst);
    // 分割通道
    vector<Mat> rgb_planes;
    split(dst, rgb_planes);

    int histSize = 255;

    float range[] = { 0, 255 };
    const float* histRange = { range };

    bool uniform = true;
    bool accumulate = false;

    Mat r_hist, g_hist, b_hist;

    // 计算直方图
    calcHist(&rgb_planes[0], 1, 0, Mat(), r_hist, 1, &histSize, &histRange, uniform, accumulate);
    calcHist(&rgb_planes[1], 1, 0, Mat(), g_hist, 1, &histSize, &histRange, uniform, accumulate);
    calcHist(&rgb_planes[2], 1, 0, Mat(), b_hist, 1, &histSize, &histRange, uniform, accumulate);

    // 创建直方图画布
    int hist_w = 500;
    int hist_h = 500;
    int bin_w = cvRound((double)hist_w / histSize);

    Mat histImage(hist_w, hist_h, CV_8UC3, Scalar(255, 255, 255));

    // 归一化
    normalize(r_hist, r_hist, 0, histImage.rows, NORM_MINMAX, -1, Mat());
    normalize(g_hist, g_hist, 0, histImage.rows, NORM_MINMAX, -1, Mat());
    normalize(b_hist, b_hist, 0, histImage.rows, NORM_MINMAX, -1, Mat());

    // 绘制直方图
    for (int i = 1; i < histSize; i++) {
        line(histImage,
            Point(bin_w*(i - 1), hist_h - cvRound(r_hist.at<float>(i - 1))),
            Point(bin_w*i, hist_h - cvRound(r_hist.at<float>(i))),
            Scalar(255, 0, 0), 1, 8, 0);
        line(histImage,
            Point(bin_w*(i - 1), hist_h - cvRound(g_hist.at<float>(i - 1))),
            Point(bin_w*i, hist_h - cvRound(g_hist.at<float>(i))),
            Scalar(0, 255, 0), 1, 8, 0);
        line(histImage,
            Point(bin_w*(i - 1), hist_h - cvRound(b_hist.at<float>(i - 1))),
            Point(bin_w*i, hist_h - cvRound(b_hist.at<float>(i))),
            Scalar(0, 0, 255), 1, 8, 0);
    }

    namedWindow(str, WINDOW_AUTOSIZE);
    imshow(str, histImage);
}

/* PSNR 峰值信噪比 */
double getPSNR(Mat& src, Mat& img) {
    Mat dst;
    
    // 矩阵对应元素作差绝对值
    absdiff(src, img, dst);
    dst.convertTo(dst, CV_32F);

    // 计算矩阵对应元素差的平方
    dst.mul(dst);
    
    Scalar s = sum(dst);
    double sse = s.val[0] + s.val[1] + s.val[2];

    if (sse <= 1e-10)
        return 0;
    else {
        double mse = sse / double(src.channels()*src.total());
        double psnr = 10.0*log10(255 * 255 / mse);
        return psnr;
    }
}

/* SSIM 结构相似性 */
Scalar getSSIM(const Mat& src, const Mat& img) {
    const double k1 = 0.01, k2 = 0.03, L = 255;
    double c1 = (k1*L)*(k1*L), c2 = (k2*L)*(k2*L);

    Mat I1, I2;
    src.convertTo(I1, CV_32F);
    img.convertTo(I2, CV_32F);
    Mat I1_2 = I1.mul(I1);
    Mat I2_2 = I2.mul(I2);
    Mat I1_I2 = I1.mul(I2);

    Mat u1, u2;
    GaussianBlur(I1, u1, Size(11, 11), 1.5);
    GaussianBlur(I2, u2, Size(11, 11), 1.5);

    Mat u1_2 = u1.mul(u1);
    Mat u2_2 = u2.mul(u2);
    Mat u1_u2 = u1.mul(u2);

    Mat sigma1_2, sigma2_2, sigma1_sigma2;
    GaussianBlur(I1_2, sigma1_2, Size(11, 11), 1.5);
    sigma1_2 -= u1_2;
    GaussianBlur(I2_2, sigma2_2, Size(11, 11), 1.5);
    sigma2_2 -= u2_2;
    GaussianBlur(I1_I2, sigma1_sigma2, Size(11, 11), 1.5);
    sigma1_sigma2 -= u1_u2;

    Mat t1, t2, t3;
    t1 = 2 * u1_u2 + c1;
    t2 = 2 * sigma1_sigma2 + c2;
    t3 = t1.mul(t2);

    t1 = u1_2 + u2_2 + c1;
    t2 = sigma1_2 + sigma2_2 + c2;
    t1 = t1.mul(t2);

    Mat ssim_map;
    divide(t3, t1, ssim_map);
    Scalar ssim = mean(ssim_map);
    return ssim;
}

/* Histogram matching 直方图匹配 */
Mat matchHistogram_Gray(Mat& img1, Mat& img2) {
    Mat src, dst;
    img1.copyTo(src);
    img2.copyTo(dst);

    double grayScaleSrc[256] = { 0 };
    double grayScaleDst[256] = { 0 };

    // 统计源图像灰度值
    for (int i = 0; i < src.rows; i++) 
        for (int j = 0; j < src.cols; j++) 
            grayScaleSrc[src.at<uchar>(i, j)]++;
        
    // 统计目标图像灰度值
    for (int i = 0; i < dst.rows; i++) 
        for (int j = 0; j < dst.cols; j++) 
            grayScaleDst[dst.at<uchar>(i, j)]++;
    
    // 计算灰度分布密度
    double densitySrc[256] = { 0 }, densityDst[256] = { 0 };
    for (int i = 0; i < 255; i++) {
        densitySrc[i] = grayScaleSrc[i] / (double)(src.rows*src.cols);
        densityDst[i] = grayScaleDst[i] / (double)(dst.rows*dst.cols);
    }
    
    // 计算累计直方图分布
    double tempSrc[256] = { 0 }, tempDst[256] = { 0 };
    tempSrc[0] = densitySrc[0];
    tempDst[0] = densityDst[0];
    for (int i = 1; i < 256; i++) {
        tempSrc[i] = tempSrc[i - 1] + densitySrc[i];
        tempDst[i] = tempDst[i - 1] + densityDst[i];
    }

    int srcMap[256] = { 0 }, dstMap[256] = { 0 };
    for (int i = 0; i < 256; i++) {
        srcMap[i] = (int)(255 * tempSrc[i] + 0.5);
        dstMap[i] = (int)(255 * tempDst[i] + 0.5);
    }

    // 构建直方图匹配灰度映射
    int grayScaleMap[256] = { 0 };
    for (int i = 0; i < 256; i++) {
        // value记录映射后的灰度值
        int value = 0;
        // value_1记录没有对应灰度值时的最接近灰度值
        int value_1 = 0;
        int sum = 0;
        int temp = srcMap[i];
        for (int j = 0; j < 256; j++) {
            if (dstMap[j] == temp) {
                value += j;
                sum++;
            }
            if (dstMap[j] > temp)
            {
                value_1 = j;
                break;
            }
        }
        if (sum == 0) {
            value = value_1;
            sum = 1;
        }
        grayScaleMap[i] = value / sum;
    }

    for (int i = 0; i < src.rows; i++)
        for (int j = 0; j <src.cols; j++) 
            src.at<uchar>(i, j) = grayScaleMap[src.at<uchar>(i, j)];
    return src;
}

Mat matchHistogram_RGB(Mat& img1, Mat& img2) {
    Mat tempSrc(img1.rows, img1.cols, CV_8UC3, Scalar(0));
    Mat tempDst(img2.rows, img2.cols, CV_8UC3, Scalar(0));
    img1.copyTo(tempSrc);
    img2.copyTo(tempDst);
    if (tempSrc.type() == CV_8UC1)
        cvtColor(tempSrc, tempSrc, CV_GRAY2RGB);
    if (tempDst.type() == CV_8UC1)
        cvtColor(tempDst, tempDst, CV_GRAY2RGB);

    Mat matArraySrc[3], matArrayDst[3], result[3];
    split(tempSrc, matArraySrc);
    split(tempDst, matArrayDst);
    
    for (int i = 0; i < 3; i++)
        result[i] = matchHistogram_Gray(matArraySrc[i], matArrayDst[i]);
    Mat dst;
    merge(result, 3, dst);
    return dst;
}

int main()
{
    Mat src, imgGray, imgRGB, imgCheck, dst;

    src = imread("./TestImages/dump.png");
    dst = imread("./TestImages/template_parking.jpg");
    if (!src.data && !dst.data)
        return -1;

    imgRGB = matchHistogram_RGB(src, dst);
    imshow("Raw", src);
    imshow("result", imgRGB);

    getHistogram(src, "RawHistogram");
    getHistogram(dst, "TemplateHistogram");
    getHistogram(imgRGB, "MatchedHistogram");

    cout << "PSNR:" << getPSNR(src, imgRGB) << "dB" << endl;
    cout << "SSIM:" << getSSIM(src, imgRGB) << endl;
    waitKey();
    return 0;
}

3.2 效果

原图-模板图-输出

直方图对比

峰值信噪比与结构相似性

四、局部直方图均衡

4.1 代码

#include <iostream>
#include "pch.h"
#include <stdio.h>
#include <opencv2/opencv.hpp>
#include <opencv2/imgproc/types_c.h>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>

using namespace std;
using namespace cv;

/* Local histogram equalization 局部直方图均衡*/
void getCDF(double* s, const double *const c) {
    for (int i = 1; i < 256; i++) {
        s[i] = s[i - 1] + c[i];
    }
}

inline void refresh(double* grayScale, int dim, int value, bool flag) {
    if (flag)
        grayScale[value] += 1.0 / (dim*dim);
    else
        grayScale[value] -= 1.0 / (dim*dim);
}

Mat equalizeHistLocal_Gray(Mat& img) {
    Mat dst(img.rows, img.cols, CV_8UC1, Scalar(0));
    int dim = 5;
    img.copyTo(dst);
    if (dst.type() == CV_8UC3)
        cvtColor(dst, dst, CV_BGR2GRAY);
        
    double grayScale[256] = { 0 };

    for (int i = 0; i < dim; i++) {
        for (int j = 0; j < dim; j++) {
            grayScale[dst.at<uchar>(i, j)] += 1.0 / (dim*dim);
        }
    }

    double s[256] = { 0 };
    s[0] = grayScale[0];
    getCDF(s, grayScale);
    int index = dst.at<uchar>(dim / 2, dim / 2);
    dst.at<uchar>(dim / 2, dim / 2) = (int)(s[index] * 255 + 0.5);
    refresh(grayScale, dim, index, false);
    refresh(grayScale, dim, dst.at<uchar>(dim / 2, dim / 2), true);

    for (int i = dim / 2; i < dst.rows - dim / 2; i++) {
        for (int j = dim / 2 + 1; j < dst.cols - dim / 2; j++) {
            bool dirty = true;

            for (int k = 0; k < dim; k++) {
                int old = dst.at<uchar>(i - dim / 2 + k, j - dim / 2 - 1);
                int add = dst.at<uchar>(i - dim / 2 + k, j + dim / 2);
                if (old != add) {
                    refresh(grayScale, dim, old, false);
                    refresh(grayScale, dim, add, true);
                    dirty = false;
                }
            }
            if (!dirty) {
                getCDF(s, grayScale);
                index = dst.at<uchar>(i, j);
                refresh(grayScale, dim, index, false);
                dst.at<uchar>(i, j) = (int)(grayScale[index] * 255 + 0.5);
                refresh(grayScale, dim, dst.at<uchar>(i, j), true);
            }
        }
    }
    return dst;
}

Mat equalizeHistLocal_RGB(Mat& img) {
    Mat temp(img.rows, img.cols, CV_8UC3, Scalar(0));
    img.copyTo(temp);
    if (temp.type() == CV_8UC1)
        cvtColor(temp, temp, CV_GRAY2RGB);

    Mat matArray[3];
    split(temp, matArray);
    for (int i = 0; i < 3; i++)
        matArray[i] = equalizeHistLocal_Gray(matArray[i]);
    Mat dst;
    merge(matArray, 3, dst);
    return dst;
}

int main()
{
    Mat src, imgGray, imgRGB, imgCheck, dst, t, a;
    src = imread("./TestImages/square.tif");
    dst = equalizeHistLocal_RGB(src);
    imshow("Raw", src);
    imshow("Equalized", dst);
    waitKey();
    return 0;
}

4.2 效果

五、基于直方图统计的局部图像增强

5.1 代码

#include <iostream>
#include "pch.h"
#include <stdio.h>
#include <opencv2/opencv.hpp>
#include <opencv2/imgproc/types_c.h>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>

using namespace std;
using namespace cv;

/* Histogram 直方图 */
void getHistogram(Mat& img, String str) {
    Mat dst(img.rows, img.cols, CV_8UC3, Scalar(0));
    img.copyTo(dst);
    // 分割通道
    vector<Mat> rgb_planes;
    split(dst, rgb_planes);

    int histSize = 255;

    float range[] = { 0, 255 };
    const float* histRange = { range };

    bool uniform = true;
    bool accumulate = false;

    Mat r_hist, g_hist, b_hist;

    // 计算直方图
    calcHist(&rgb_planes[0], 1, 0, Mat(), r_hist, 1, &histSize, &histRange, uniform, accumulate);
    calcHist(&rgb_planes[1], 1, 0, Mat(), g_hist, 1, &histSize, &histRange, uniform, accumulate);
    calcHist(&rgb_planes[2], 1, 0, Mat(), b_hist, 1, &histSize, &histRange, uniform, accumulate);

    // 创建直方图画布
    int hist_w = 500;
    int hist_h = 500;
    int bin_w = cvRound((double)hist_w / histSize);

    Mat histImage(hist_w, hist_h, CV_8UC3, Scalar(255, 255, 255));

    // 归一化
    normalize(r_hist, r_hist, 0, histImage.rows, NORM_MINMAX, -1, Mat());
    normalize(g_hist, g_hist, 0, histImage.rows, NORM_MINMAX, -1, Mat());
    normalize(b_hist, b_hist, 0, histImage.rows, NORM_MINMAX, -1, Mat());

    // 绘制直方图
    for (int i = 1; i < histSize; i++) {
        line(histImage,
            Point(bin_w*(i - 1), hist_h - cvRound(r_hist.at<float>(i - 1))),
            Point(bin_w*i, hist_h - cvRound(r_hist.at<float>(i))),
            Scalar(255, 0, 0), 1, 8, 0);
        line(histImage,
            Point(bin_w*(i - 1), hist_h - cvRound(g_hist.at<float>(i - 1))),
            Point(bin_w*i, hist_h - cvRound(g_hist.at<float>(i))),
            Scalar(0, 255, 0), 1, 8, 0);
        line(histImage,
            Point(bin_w*(i - 1), hist_h - cvRound(b_hist.at<float>(i - 1))),
            Point(bin_w*i, hist_h - cvRound(b_hist.at<float>(i))),
            Scalar(0, 0, 255), 1, 8, 0);
    }

    namedWindow(str, WINDOW_AUTOSIZE);
    imshow(str, histImage);
}

/* 直方图均衡 Histogram Equalization*/
Mat equalizeHist_Gray(Mat& img) {
    Mat dst(img.rows, img.cols, CV_8UC1, Scalar(0));
    img.copyTo(dst);
    if (dst.type() == CV_8UC3)
        cvtColor(dst, dst, CV_BGR2GRAY);

    // 计算灰度值
    double grayScale[256] = { 0 };
    for (int i = 0; i < dst.rows; i++)
        for (int j = 0; j < dst.cols; j++)
            grayScale[dst.at<uchar>(i, j)]++;

    // 计算灰度分布密度
    int pixels = dst.rows*dst.cols;
    double density[256] = { 0 };

    for (int i = 0; i < 256; i++)
        density[i] = grayScale[i] / (double)pixels;

    // 计算累计直方图分布
    double temp[256] = { 0 };

    temp[0] = density[0];

    for (int i = 1; i < 256; i++)
        temp[i] = temp[i - 1] + density[i];

    // 累计分布取整
    int result[256] = { 0 };

    for (int i = 0; i < 256; i++)
        result[i] = (int)(255 * temp[i] + 0.5);

    // 灰度值映射
    for (int i = 0; i < dst.rows; i++)
        for (int j = 0; j < dst.cols; j++)
            dst.at<uchar>(i, j) = result[dst.at<uchar>(i, j)];

    return dst;
}

Mat equalizeHist_RGB(Mat& img) {
    Mat temp(img.rows, img.cols, CV_8UC3, Scalar(0));
    img.copyTo(temp);
    if (temp.type() == CV_8UC1)
        cvtColor(temp, temp, CV_GRAY2RGB);

    Mat matArray[3];
    split(temp, matArray);
    for (int i = 0; i < 3; i++)
        matArray[i] = equalizeHist_Gray(matArray[i]);
    Mat dst;
    merge(matArray, 3, dst);
    return dst;
}

/* PSNR 峰值信噪比 */
double getPSNR(Mat& src, Mat& img) {
    Mat dst;

    // 矩阵对应元素作差绝对值
    absdiff(src, img, dst);
    dst.convertTo(dst, CV_32F);

    // 计算矩阵对应元素差的平方
    dst.mul(dst);

    Scalar s = sum(dst);
    double sse = s.val[0] + s.val[1] + s.val[2];

    if (sse <= 1e-10)
        return 0;
    else {
        double mse = sse / double(src.channels()*src.total());
        double psnr = 10.0*log10(255 * 255 / mse);
        return psnr;
    }
}

/* SSIM 结构相似性 */
Scalar getSSIM(const Mat& src, const Mat& img) {
    const double k1 = 0.01, k2 = 0.03, L = 255;
    double c1 = (k1*L)*(k1*L), c2 = (k2*L)*(k2*L);

    Mat I1, I2;
    src.convertTo(I1, CV_32F);
    img.convertTo(I2, CV_32F);
    Mat I1_2 = I1.mul(I1);
    Mat I2_2 = I2.mul(I2);
    Mat I1_I2 = I1.mul(I2);

    Mat u1, u2;
    GaussianBlur(I1, u1, Size(11, 11), 1.5);
    GaussianBlur(I2, u2, Size(11, 11), 1.5);

    Mat u1_2 = u1.mul(u1);
    Mat u2_2 = u2.mul(u2);
    Mat u1_u2 = u1.mul(u2);

    Mat sigma1_2, sigma2_2, sigma1_sigma2;
    GaussianBlur(I1_2, sigma1_2, Size(11, 11), 1.5);
    sigma1_2 -= u1_2;
    GaussianBlur(I2_2, sigma2_2, Size(11, 11), 1.5);
    sigma2_2 -= u2_2;
    GaussianBlur(I1_I2, sigma1_sigma2, Size(11, 11), 1.5);
    sigma1_sigma2 -= u1_u2;

    Mat t1, t2, t3;
    t1 = 2 * u1_u2 + c1;
    t2 = 2 * sigma1_sigma2 + c2;
    t3 = t1.mul(t2);

    t1 = u1_2 + u2_2 + c1;
    t2 = sigma1_2 + sigma2_2 + c2;
    t1 = t1.mul(t2);

    Mat ssim_map;
    divide(t3, t1, ssim_map);
    Scalar ssim = mean(ssim_map);
    return ssim;
}

/* 基于直方图统计的局部增强 */
// 计算概率均值
double getMean(double* pdf) {
    double mean = 0;
    for (int i = 0; i < 256; i++)
        mean += pdf[i] * i;
    return mean;
}

// 计算方差
double getVariance(double* pdf, double mean) {
    double n = 0;
    for (int i = 0; i < 256; i++)
        if (pdf[i] != 0)
            n += (pow((i - mean), 2)*pdf[i]);
    return n;
}

void init(double* pdf) {
    for (int i = 0; i < 256; i++) 
        pdf[i] = 0;
}

Mat statisticsHistogram_Gray(Mat& img, double E, double k0, double k1, double k2) {
    Mat dst(img.rows, img.cols, CV_8UC1, Scalar(0));
    img.copyTo(dst);
    if (dst.type() == CV_8UC3)
        cvtColor(dst, dst, CV_BGR2GRAY);
    int dim = 3;

    double grayScale[256] = { 0 };
    for (int i = 0; i < img.rows; i++)
        for (int j = 0; j < img.cols; j++)
            grayScale[img.at<uchar>(i, j)] += 1.0 / (img.rows*img.cols);

    double mean = getMean(grayScale);

    double SigmaG = sqrt(getVariance(grayScale, mean));

    double Mxy = 0, Sigmaxy = 0;
    for (int i = dim / 2; i < img.rows - dim / 2; i++) {
        for (int j = dim / 2; j < img.cols - dim / 2; j++) {
            init(grayScale);
            for (int m = i - dim / 2; m < i + dim / 2 + 1; m++) {
                for (int n = j - dim / 2; n < j + dim / 2 + 1; n++) {
                    grayScale[img.at<uchar>(m, n)] += 1.0 / (dim*dim);
                }
            }
            
            Mxy = getMean(grayScale);
            Sigmaxy = sqrt(getVariance(grayScale, Mxy));

            if (Mxy <= k0 * mean && k1*SigmaG <= Sigmaxy && Sigmaxy <= k2 * SigmaG) 
                dst.at<uchar>(i, j) = img.at<uchar>(i, j)*E;
        }
    }
    return dst;
}

Mat statisticsHistogram_RGB(Mat& img, double E, double k0, double k1, double k2) {
    Mat temp(img.rows, img.cols, CV_8UC3, Scalar(0));
    img.copyTo(temp);
    if (temp.type() == CV_8UC1)
        cvtColor(temp, temp, CV_GRAY2RGB);

    Mat matArray[3];
    split(temp, matArray);
    for (int i = 0; i < 3; i++)
        matArray[i] = statisticsHistogram_Gray(matArray[i], E, k0, k1, k2);
    Mat dst;
    merge(matArray, 3, dst);
    return dst;
}

int main()
{
    Mat src, imgGray, imgRGB, imgCheck, dst, t, a;
    src = imread("./TestImages/Fig0327(a)(tungsten_original).tif");
    t = equalizeHist_RGB(src);
    dst = statisticsHistogram_RGB(src, 20, 0.4, 0.02, 0.4);
    imshow("Raw", src);
    imshow("Enhanced", dst);
    imshow("Equalized", t);
    cout << "(RAW/Equalized)PSNR:" << getPSNR(src, t) << "dB" << endl;
    cout << "(RAW/Enhanced)PSNR:" << getPSNR(src, dst) << "dB" << endl;
    cout << "(RAW/Equalized)SSIM:" << getSSIM(src, t) << endl;
    cout << "(RAW/Enhanced)SSIM:" << getSSIM(src, dst) << endl;

    waitKey();
    return 0;
}

5.2 效果

原图-全局直方图均衡-基于直方图统计的局部增强

峰值信噪比与结构相似性

基于直方图统计的局部增强

参考链接:
http://www.opencv.org.cn/opencvdoc/2.3.2/html/doc/tutorials/imgproc/histograms/histogram_calculation/histogram_calculation.html
http://www.cnblogs.com/brucemu/archive/2013/10/17/3374558.html
https://blog.csdn.net/u013263891/article/details/82987417

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,761评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,953评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,998评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,248评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,130评论 4 356
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,145评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,550评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,236评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,510评论 1 291
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,601评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,376评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,247评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,613评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,911评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,191评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,532评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,739评论 2 335

推荐阅读更多精彩内容