图像直方图

图象直方图的统计与绘制

图像直方图函数

images是数组,可以统计多个图像的直方图 accumulate如果选择是,则把多个通道的数据放在一张直方图里面

1维的直方图矩阵是一个1×256的矩阵

示例

#include <opencv2\opencv.hpp>
#include <iostream>

using namespace cv;
using namespace std;

int main()
{
    Mat img = imread("apple.jpg");
    if (img.empty())
    {
        cout << "请确认图像文件名称是否正确" << endl;
        return -1;
    }
    Mat gray;
    cvtColor(img, gray, COLOR_BGR2GRAY);
    //设置提取直方图的相关变量
    Mat hist;  //用于存放直方图计算结果
    const int channels[1] = { 0 };  //通道索引
    float inRanges[2] = { 0,255 };
    const float* ranges[1] = { inRanges };  //像素灰度值范围
    const int bins[1] = { 256 };  //直方图的维度,其实就是像素灰度值的最大值
    calcHist(&gray, 1, channels, Mat(), hist, 1, bins, ranges);  //计算图像直方图
    //准备绘制直方图
    int hist_w = 512;
    int hist_h = 400;
    int width = 2;//把每个直方图宽度定义为2个像素
    Mat histImage = Mat::zeros(hist_h, hist_w, CV_8UC3);//全黑图像
    for (int i = 1; i <= hist.rows; i++)
    {
        rectangle(histImage, Point(width * (i - 1), hist_h - 1),
            Point(width * i - 1, hist_h - cvRound(hist.at<float>(i - 1) / 15)),//cvRound用于把直方图中的浮点数转化为整数
            Scalar(255, 255, 255), -1);//除以15防止像素值过大放不下
    }
    namedWindow("histImage", WINDOW_AUTOSIZE);
    imshow("histImage", histImage);
    imshow("gray", gray);
    waitKey(0);
    return 0;
}

OPENCV4不能直接绘制直方图 需要自己画 然而在部分情况,直方图可能会出现部分像素数量太多导致无法显示完全 可以用归一化的方法 找到图像最高值,并设置为1,使其他像素值与其比较 示例

#include <opencv2\opencv.hpp>
#include <iostream>

using namespace cv;
using namespace std;

int main()
{
    system("color F0");  //更改输出界面颜色
    vector<double> positiveData = { 2.0, 8.0, 10.0 };
    vector<double> normalized_L1, normalized_L2, normalized_Inf, normalized_L2SQR;
    //测试不同归一化方法
    normalize(positiveData, normalized_L1, 1.0, 0.0, NORM_L1);  //绝对值求和归一化
    cout << "normalized_L1=[" << normalized_L1[0] << ", "
        << normalized_L1[1] << ", " << normalized_L1[2] << "]" << endl;
    normalize(positiveData, normalized_L2, 1.0, 0.0, NORM_L2);  //模长归一化
    cout << "normalized_L2=[" << normalized_L2[0] << ", "
        << normalized_L2[1] << ", " << normalized_L2[2] << "]" << endl;
    normalize(positiveData, normalized_Inf, 1.0, 0.0, NORM_INF);  //最大值归一化
    cout << "normalized_Inf=[" << normalized_Inf[0] << ", "
        << normalized_Inf[1] << ", " << normalized_Inf[2] << "]" << endl;
    normalize(positiveData, normalized_L2SQR, 1.0, 0.0, NORM_MINMAX);  //偏移归一化
    cout << "normalized_MINMAX=[" << normalized_L2SQR[0] << ", "
        << normalized_L2SQR[1] << ", " << normalized_L2SQR[2] << "]" << endl;
    //将图像直方图归一化
    Mat img = imread("apple.jpg");
    if (img.empty())
    {
        cout << "请确认图像文件名称是否正确" << endl;
        return -1;
    }
    Mat gray, hist;
    cvtColor(img, gray, COLOR_BGR2GRAY);
    const int channels[1] = { 0 };
    float inRanges[2] = { 0,255 };
    const float* ranges[1] = { inRanges };
    const int bins[1] = { 256 };
    calcHist(&gray, 1, channels, Mat(), hist, 1, bins, ranges);
    int hist_w = 512;
    int hist_h = 400;
    int width = 2;
    Mat histImage_L1 = Mat::zeros(hist_h, hist_w, CV_8UC3);
    Mat histImage_Inf = Mat::zeros(hist_h, hist_w, CV_8UC3);
    Mat hist_L1, hist_Inf;
    normalize(hist, hist_L1, 1, 0, NORM_L1, -1, Mat());
    for (int i = 1; i <= hist_L1.rows; i++)
    {
        rectangle(histImage_L1, Point(width * (i - 1), hist_h - 1),
            Point(width * i - 1, hist_h - cvRound(30 * hist_h * hist_L1.at<float>(i - 1)) - 1),
            Scalar(255, 255, 255), -1);
    }
    normalize(hist, hist_Inf, 1, 0, NORM_INF, -1, Mat());
    for (int i = 1; i <= hist_Inf.rows; i++)
    {
        rectangle(histImage_Inf, Point(width * (i - 1), hist_h - 1),
            Point(width * i - 1, hist_h - cvRound(hist_h * hist_Inf.at<float>(i - 1)) - 1),
            Scalar(255, 255, 255), -1);
    }
    imshow("histImage_L1", histImage_L1);
    imshow("histImage_Inf", histImage_Inf);
    waitKey(0);
    return 0;
}

图像直方图的均衡化

直方图均衡化

输入图和输出图像都是单通道的 将原先的直方图的分布均衡化,先前的直方图可能由于部分直方图像素过度集中导致损失部分纹理,利用直方图均衡化的方法可以让集中的直方图均衡化,回复部分纹理

#include <opencv2\opencv.hpp>
#include <iostream>

using namespace cv;
using namespace std;

void drawHist(Mat& hist, int type, string name)  //归一化并绘制直方图函数
{
    int hist_w = 512;
    int hist_h = 400;
    int width = 2;
    Mat histImage = Mat::zeros(hist_h, hist_w, CV_8UC3);
    normalize(hist, hist, 1, 0, type, -1, Mat());
    for (int i = 1; i <= hist.rows; i++)
    {
        rectangle(histImage, Point(width * (i - 1), hist_h - 1),
            Point(width * i - 1, hist_h - cvRound(hist_h * hist.at<float>(i - 1)) - 1),
            Scalar(255, 255, 255), -1);
    }
    imshow(name, histImage);
}
//主函数
int main()
{
    Mat img = imread("histMatch.png");
    if (img.empty())
    {
        cout << "请确认图像文件名称是否正确" << endl;
        return -1;
    }
    Mat gray, hist, hist2;
    cvtColor(img, gray, COLOR_BGR2GRAY);
    Mat equalImg;
    equalizeHist(gray, equalImg);  //将图像直方图均衡化
    const int channels[1] = { 0 };
    float inRanges[2] = { 0,255 };
    const float* ranges[1] = { inRanges };
    const int bins[1] = { 256 };
    calcHist(&gray, 1, channels, Mat(), hist, 1, bins, ranges);
    calcHist(&equalImg, 1, channels, Mat(), hist2, 1, bins, ranges);
    drawHist(hist, NORM_INF, "hist");
    drawHist(hist2, NORM_INF, "hist2");
    imshow("原图", gray);
    imshow("均衡化后的图像", equalImg);
    waitKey(0);
    return 0;
}

直方图匹配

原理 通过原直方图和目标直方图的累积概率差值比较最小值可以得到映射关系 一个存放原直方图和目标直方图的累积概率差值矩阵 通过差值最小值得到LUT映射关系 应用LUT实现直方图匹配 非常坏消息是,OPENCV4并没有给出直方图匹配的函数,所以需要自己写 直方图匹配不能改变尖峰的存在 示例 [[OPENCV4L3 图像颜色处理#LUT查找表|LUT查找表]]

#include <opencv2\opencv.hpp>
#include <iostream>

using namespace cv;
using namespace std;

void drawHist(Mat& hist, int type, string name)  //归一化并绘制直方图函数
{
    int hist_w = 512;
    int hist_h = 400;
    int width = 2;
    Mat histImage = Mat::zeros(hist_h, hist_w, CV_8UC3);
    normalize(hist, hist, 1, 0, type, -1, Mat());
    for (int i = 1; i <= hist.rows; i++)
    {
        rectangle(histImage, Point(width * (i - 1), hist_h - 1),
            Point(width * i - 1, hist_h - cvRound(20 * hist_h * hist.at<float>(i - 1)) - 1),
            Scalar(255, 255, 255), -1);
    }
    imshow(name, histImage);
}
//主函数
int main()
{
    Mat img1 = imread("histMatch.png");
    Mat img = imread("Lena.png");
    Mat img2;
    cvtColor(img, img2, COLOR_BGR2GRAY);//灰度lena的超完美图像,可以拿来做模板
    if (img1.empty() || img2.empty())
    {
        cout << "请确认图像文件名称是否正确" << endl;
        return -1;
    }
    Mat hist1, hist2;//存放直方图
    //计算两张图像直方图
    const int channels[1] = { 0 };
    float inRanges[2] = { 0,255 };
    const float* ranges[1] = { inRanges };
    const int bins[1] = { 256 };
    calcHist(&img1, 1, channels, Mat(), hist1, 1, bins, ranges);
    calcHist(&img2, 1, channels, Mat(), hist2, 1, bins, ranges);
    //归一化两张图像的直方图
    drawHist(hist1, NORM_L1, "hist1");
    drawHist(hist2, NORM_L1, "hist2");
    //计算两张图像直方图的累积概率
    float hist1_cdf[256] = { hist1.at<float>(0) };
    float hist2_cdf[256] = { hist2.at<float>(0) };
    for (int i = 1; i < 256; i++)
    {
        hist1_cdf[i] = hist1_cdf[i - 1] + hist1.at<float>(i);//由于已经归一化了,直方图相加的累计概率
        hist2_cdf[i] = hist2_cdf[i - 1] + hist2.at<float>(i);

    }
    //构建累积概率误差矩阵
    float diff_cdf[256][256];
    for (int i = 0; i < 256; i++)
    {
        for (int j = 0; j < 256; j++)
        {
            diff_cdf[i][j] = fabs(hist1_cdf[i] - hist2_cdf[j]);//防止出现负值
        }
    }

    //生成LUT映射表
    Mat lut(1, 256, CV_8U);
    for (int i = 0; i < 256; i++)
    {
        // 查找源灰度级为i的映射灰度
        // 和i的累积概率差值最小的规定化灰度
        float min = diff_cdf[i][0];
        int index = 0;
        //寻找累积概率误差矩阵中每一行中的最小值
        for (int j = 1; j < 256; j++)
        {
            if (min > diff_cdf[i][j])
            {
                min = diff_cdf[i][j];
                index = j;
            }
        }
        lut.at<uchar>(i) = (uchar)index;//把像素值为i的部分映射到像素值为index的地方
    }
    Mat result, hist3;
    LUT(img1, lut, result);
    imshow("待匹配图像", img1);
    imshow("匹配的模板图像", img2);
    imshow("直方图匹配结果", result);
    calcHist(&result, 1, channels, Mat(), hist3, 1, bins, ranges);
    drawHist(hist3, NORM_L1, "hist3");  //绘制匹配后的图像直方图
    waitKey(0);
    return 0;
}

图像的模板匹配

对8×8的矩阵里面对4×4的模板进行匹配求系数,可以得到一个5×5的系数矩阵,系数矩阵的位置是由左上角的数来决定的

示例

#include <opencv2\opencv.hpp>
#include <iostream>

using namespace cv;
using namespace std;

int main()
{
    Mat img = imread("lena.png");
    Mat temp = imread("lena_face.png");
    if (img.empty() || temp.empty())
    {
        cout << "请确认图像文件名称是否正确" << endl;
        return -1;
    }
    Mat result;
    matchTemplate(img, temp, result, TM_CCOEFF_NORMED);//模板匹配
    double maxVal, minVal;
    Point minLoc, maxLoc;
    //寻找匹配结果中的最大值和最小值以及坐标位置
    minMaxLoc(result, &minVal, &maxVal, &minLoc, &maxLoc);//寻找最大最小值位置
    //绘制最佳匹配区域
    //rectangle(img, cv::Rect(maxLoc.x, maxLoc.y, temp.cols, temp.rows), Scalar(0, 0, 255), 2);//另一个形式
    rectangle(img, Point(maxLoc.x, maxLoc.y), Point(maxLoc.x + temp.cols, maxLoc.y + temp.rows), Scalar(0, 0, 255), 2);//绘制矩形
    imshow("原图", img);
    imshow("模板图像", temp);
    imshow("result", result);
    waitKey(0);
    return 0;
}