图像处理学习笔记之直方图的计算与绘制

图像直方图包含丰富的图像细节信息,反映了图像像素点的概率分布情况,它统计了每一个强度值具有的像素个数。灰度级范围是[0,L-1]的数字图像的直方图是离散函数h(rk)=nk,其中是rk第k级灰度值,nk是图像中灰度为rk的像素个数。在实践中,经常用乘积MN表示的图像像素总数除它的每个分量来归一化直方图,MN是图像的行列数。因此归一化后的直方图由p(rk)=nk/MN给出。直方图的横坐标表示灰度级,纵坐标表示图像中该灰度级出现的次数(频率)。

一般来说,在暗图像中,直方图的分量集中在灰度级较低的一侧。亮图像的直方图分量集中在灰度级值较高的一侧。低对比度的图像具有较窄的直方图,且集中于灰度级的中部。高对比度的图像中直方图的分量覆盖了很宽的灰度级范围。

图像处理学习笔记之直方图的计算与绘制_第1张图片

图像处理学习笔记之直方图的计算与绘制_第2张图片

图1 亮图像及其灰度直方图

图像处理学习笔记之直方图的计算与绘制_第3张图片

图像处理学习笔记之直方图的计算与绘制_第4张图片

图2 暗图像及其灰度直方图

图像处理学习笔记之直方图的计算与绘制_第5张图片

图像处理学习笔记之直方图的计算与绘制_第6张图片

图3 高对比度图像及其灰度直方图

图像处理学习笔记之直方图的计算与绘制_第7张图片

图像处理学习笔记之直方图的计算与绘制_第8张图片

图4 低对比度图像及其灰度直方图

opencv中提供了calchist函数用于计算图像的直方图。其声明如下:

void calcHist(const Mat* arrays, int narrays, const int* channels, InputArray mask, OutputArray hist, int dims, const int* histSize, const float** ranges, bool uniform=true, bool accumulate=false );

  • arrays:源输入图像数组,可以是多幅图像,所有的图像必须有同样的深度(CV_8U or CV_32F),同时一副图像可以有多个channes。
  • narrays:源输入数组中的元素个数
  • channels:用来计算直方图的通道维数数组,第一个数组的通道由0到arrays[0].channels()-1列出,第二个数组的通道从arrays[0].channels()到arrays[0].channels()+arrays[1].channels()-1以此类推
  • mask:可选的掩膜,如果该矩阵不是空的,则必须是8位的并且与arrays[i]的大小相等,掩膜的非零值标记需要在直方图中统计的数组元素;
  • hist:输出直方图,是一个稠密或者稀疏的dims维的数组
  • dims:直方图的维数,必须为正,并且不大于CV_MAX_DIMS(当前的OpenCV版本中为32,即最大可以统计32维的直方图);
  • histSize:用于指出直方图数组每一维的大小的数组,即指出每一维的bin的个数的数组
  • ranges:用于指出直方图每一维的每个bin的上下界范围数组的数组,当直方图是均匀的(uniform =true)时,对每一维i指定直方图的第0个bin的下界(包含即[)L0和最后一个即第histSize[i]-1个bin的上界(不包含的即))U_histSize[i]-1,也就是说对均匀直方图来说,每一个ranges[i]都是一个两个元素的数组【指出该维的上下界】。当直方图不是均匀的时,每一个ranges[i]数组都包含histSize[i]+1个元素:L0,U0=L1,U1=L1,...,U_histSize[i]-2 = L_histSize[i]-1,U_histSize[i]-1.不在L0到U_histSize[i]-1之间的数组元素将不会统计进直方图中
  • uniform:直方图是否均匀的标志;【指定直方图每个bin统计的是否是相同数量的灰度级】
  • accumulate:累加标
int main()
{
	Mat src, dst;
	src = imread("1.jpg");
	if (!src.data)
	{
		return -1;
	}
	/// 通道分离
	vector bgr_planes;
	split(src, bgr_planes);
	int histSize = 256;
	/// 设置范围
	float range[] = { 0, 256 };
	const float* histRange = { range };
	bool uniform = true; bool accumulate = false;
	Mat b_hist, g_hist, r_hist;
	/// 计算直方图:
	calcHist(&bgr_planes[0], 1, 0, Mat(), b_hist, 1, &histSize, &histRange, uniform, accumulate);
	calcHist(&bgr_planes[1], 1, 0, Mat(), g_hist, 1, &histSize, &histRange, uniform, accumulate);
	calcHist(&bgr_planes[2], 1, 0, Mat(), r_hist, 1, &histSize, &histRange, uniform, accumulate);
	// 创建画布
	int hist_w = 512; int hist_h = 400;
	int bin_w = cvRound((double)hist_w / histSize);
	Mat histImage(hist_h, hist_w, CV_8UC3, Scalar(0, 0, 0));
	/// 归一化到 [ 0, histImage.rows ]
	normalize(b_hist, b_hist, 0, histImage.rows, NORM_MINMAX, -1, Mat());
	normalize(g_hist, g_hist, 0, histImage.rows, NORM_MINMAX, -1, Mat());
	normalize(r_hist, r_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(b_hist.at(i - 1))),
			Point(bin_w*(i), hist_h - cvRound(b_hist.at(i))),
			Scalar(255, 0, 0), 2, 8, 0);
		line(histImage, Point(bin_w*(i - 1), hist_h - cvRound(g_hist.at(i - 1))),
			Point(bin_w*(i), hist_h - cvRound(g_hist.at(i))),
			Scalar(0, 255, 0), 2, 8, 0);
		line(histImage, Point(bin_w*(i - 1), hist_h - cvRound(r_hist.at(i - 1))),
			Point(bin_w*(i), hist_h - cvRound(r_hist.at(i))),
			Scalar(0, 0, 255), 2, 8, 0);
	}
	namedWindow("calcHist Demo", CV_WINDOW_AUTOSIZE);
	imshow("calcHist Demo", histImage);
	waitKey(0);
	return 0;
}

以下是使用QChart进行直方图显示的代码。对彩色图像分通道处理,当鼠标移动到指定位置时,还可以显示横纵坐标。

图像处理学习笔记之直方图的计算与绘制_第9张图片

 使用QLineSerial绘制折线,使用QScatterseries进行离散点的绘制,QScatterseries的hovered()信号中捕捉点的坐标。本代码使用QT6.2.2实现,没有使用QChart中已被弃用的函数。要注意的是,多个数据在同一坐标系中显示,后添加的数据必须设置attachAxis()进行坐标系的绑定,否则数据显示将会发生错误。

#include "histdialog.h"
#include 
#include 
#include 
#include 
#include 
int rangeY = 0;
HistDialog::HistDialog(cv::Mat &mat, QWidget *parent) :
    QDialog(parent), maxVal(0)
{
    setWindowTitle("灰度直方图");
    sourceImg = mat;
    RcheckBox = new QCheckBox("R");
    GcheckBox = new QCheckBox("G");
    BcheckBox = new QCheckBox("B");

    imageHist(sourceImg.channels(), 256);
    seriesR = new QLineSeries();
    seriesG = new QLineSeries();
    seriesB = new QLineSeries();
    chart = new QChart();
    chart->legend()->hide();

    ScatterseriesR1 = new QScatterSeries();
    ScatterseriesR1->setMarkerShape(QScatterSeries::MarkerShapeCircle);//圆形的点
    ScatterseriesR1->setBorderColor(QColor(Qt::red)); //离散点边框颜色
    ScatterseriesR1->setBrush(QBrush(QColor(Qt::red)));//离散点背景色
    ScatterseriesR1->setMarkerSize(2); //离散点大小

    ScatterseriesG1 = new QScatterSeries();
    ScatterseriesG1->setMarkerShape(QScatterSeries::MarkerShapeCircle);//圆形的点
    ScatterseriesG1->setBorderColor(QColor(Qt::green)); //离散点边框颜色
    ScatterseriesG1->setBrush(QBrush(QColor(Qt::green)));//离散点背景色
    ScatterseriesG1->setMarkerSize(2); //离散点大小

    ScatterseriesB1 = new QScatterSeries();
    ScatterseriesB1->setMarkerShape(QScatterSeries::MarkerShapeCircle);//圆形的点
    ScatterseriesB1->setBorderColor(QColor(Qt::blue)); //离散点边框颜色
    ScatterseriesB1->setBrush(QBrush(QColor(Qt::blue)));//离散点背景色
    ScatterseriesB1->setMarkerSize(2); //离散点大小
    //设置横纵坐标
    QValueAxis *axisX = new QValueAxis;
    axisX->setRange(0, 255);
    axisX->setTickType(QValueAxis::TickType::TicksDynamic);
    axisX->setTickInterval(20);
    axisX->setLabelFormat("%d");

    QValueAxis *axisY = new QValueAxis;
    axisY->setRange(0, maxVal);
    axisY->setTickType(QValueAxis::TickType::TicksDynamic);
    axisY->setTickInterval(400);
    axisY->setLabelFormat("%d");

    if(sourceImg.channels() == 1)
    {
        for(int i = 0; i < 256; i++)
        {
            seriesR->append(i, RChannelhist.at(i));
            ScatterseriesR1->append(i, RChannelhist.at(i));
        }
        seriesR->setColor(Qt::red);
        chart->addSeries(seriesR);
        chart->addSeries(ScatterseriesR1);
        chart->addAxis(axisX, Qt::AlignBottom);
        chart->addAxis(axisY, Qt::AlignLeft);
    }
    else if(sourceImg.channels() == 3)
    {
        for(int i = 0; i < 256; i++)
        {
            seriesR->append(i, RChannelhist.at(i));
            ScatterseriesR1->append(i, RChannelhist.at(i));
            seriesG->append(i, GChannelhist.at(i));
            ScatterseriesG1->append(i, GChannelhist.at(i));
            seriesB->append(i, BChannelhist.at(i));
            ScatterseriesB1->append(i, BChannelhist.at(i));
        }
        seriesR->setColor(Qt::red);
        seriesG->setColor(Qt::green);
        seriesB->setColor(Qt::blue);
        chart->addSeries(seriesR);
        chart->addSeries(ScatterseriesR1);
        chart->addAxis(axisX, Qt::AlignBottom);
        chart->addAxis(axisY, Qt::AlignLeft);
        chart->addSeries(seriesG);
        chart->addSeries(ScatterseriesG1);
        seriesG->attachAxis(axisX);//各个通道使用同一坐标系显示
        seriesG->attachAxis(axisY);
        ScatterseriesG1->attachAxis(axisX);
        ScatterseriesG1->attachAxis(axisY);
        chart->addSeries(seriesB);
        chart->addSeries(ScatterseriesB1);
        seriesB->attachAxis(axisX);
        seriesB->attachAxis(axisY);
        ScatterseriesB1->attachAxis(axisX);
        ScatterseriesB1->attachAxis(axisY);
    }
    area = new QChartView(chart);
    area->setRenderHint(QPainter::Antialiasing);
    area->setMouseTracking(true);
    area->setMinimumSize(600, 500);
    m_valueLabel = new QLabel(area);
    m_valueLabel->setFixedSize(80, 30);
    m_valueLabel->setAutoFillBackground(true);
    m_valueLabel->setStyleSheet(QString("QLabel{color:#000000; font-family:\"Microsoft Yahei\"; font-size:12px;"
                                        " background-color:rgba(21, 100, 255, 51); border-radius:4px; text-align:center;}"));
    m_valueLabel->setAlignment(Qt::AlignHCenter | Qt::AlignVCenter);
    m_valueLabel->hide();
    mainLayout = new QGridLayout(this);
    mainLayout->addWidget(area, 0, 0, 1, 3);
    mainLayout->addWidget(RcheckBox, 1, 0, 1, 1, Qt::AlignCenter);
    mainLayout->addWidget(GcheckBox, 1, 1, 1, 1, Qt::AlignCenter);
    mainLayout->addWidget(BcheckBox, 1, 2, 1, 1, Qt::AlignCenter);
    connect (RcheckBox, SIGNAL(stateChanged(int)), this, SLOT(imhist(int)));
    connect (GcheckBox, SIGNAL(stateChanged(int)), this, SLOT(imhist(int)));
    connect (BcheckBox, SIGNAL(stateChanged(int)), this, SLOT(imhist(int)));
    setAttribute(Qt::WA_DeleteOnClose);
    connect(ScatterseriesR1, SIGNAL(hovered(const QPointF &, bool)), this, SLOT(dataShow(const QPointF&, bool)));
    connect(ScatterseriesG1, SIGNAL(hovered(const QPointF &, bool)), this, SLOT(dataShow(const QPointF&, bool)));
    connect(ScatterseriesB1, SIGNAL(hovered(const QPointF &, bool)), this, SLOT(dataShow(const QPointF&, bool)));

}

HistDialog::~HistDialog()
{

}

void HistDialog::imageHist(int chn, int N)
{
    //设定直方图参数
    const int channels[1] = {0};
    const int histSize[1] = {N};
    float pranges[2] = {0.0, 255.0};
    const float* ranges[1] = {pranges};
    double maxVal_R = 0.0;
    double maxVal_G = 0.0;
    double maxVal_B = 0.0;
    if(chn == 1)
    {
        RcheckBox->setChecked(true);
        GcheckBox->setEnabled(false);
        BcheckBox->setEnabled(false);

        cv::calcHist(&sourceImg, 1, channels, cv::Mat(), RChannelhist, 1, histSize, ranges);
        cv::minMaxLoc(RChannelhist, 0, &maxVal, 0, 0);
    }
    else if(chn == 3)
    {
        //对于彩色图像,分离各个通道进行单独计算
        std::vector rgb_planes;
        cv::split(sourceImg, rgb_planes);
        cv::calcHist(&rgb_planes[0], 1, channels, cv::Mat(), RChannelhist, 1, histSize, ranges);
        cv::calcHist(&rgb_planes[1], 1, channels, cv::Mat(), GChannelhist, 1, histSize, ranges);
        cv::calcHist(&rgb_planes[2], 1, channels, cv::Mat(), BChannelhist, 1, histSize, ranges);
        cv::minMaxLoc(RChannelhist, 0, &maxVal_R, 0, 0);
        cv::minMaxLoc(GChannelhist, 0, &maxVal_G, 0, 0);
        cv::minMaxLoc(BChannelhist, 0, &maxVal_B, 0, 0);
        maxVal = std::max(maxVal_R, std::max(maxVal_G, maxVal_B));
        RcheckBox->setChecked(true);
        GcheckBox->setChecked(true);
        BcheckBox->setChecked(true);
    }
    //判断最大值所处的范围
    //获取最大值的位数
    QString numStr = QString::number(int(maxVal));
    int numsize = numStr.size();
    //获取前两位数
    QString firstTwoStr = numStr.left(2);
    int firstTwoNum = firstTwoStr.toInt();
    QString firstStr = numStr.left(1);
    int firstNum = firstStr.toInt();
    QString secondStr = firstTwoStr.right(1);
    int secondNum = secondStr.toInt();
    if(secondNum < 5)
    {
        secondNum = 5;
        firstTwoNum = firstNum * 10 + 5;
    }
    else
    {
        firstTwoNum = (firstNum + 1) * 10;
    }
    rangeY = firstTwoNum * pow(10, numsize - 2);
}

void HistDialog::imhist(int i)
{
    if(RcheckBox->isChecked())
    {
        seriesR->setVisible(true);
        ScatterseriesR1->setVisible(true);
    }
    else
    {
        seriesR->setVisible(false);
        ScatterseriesR1->setVisible(false);
    }
    if(GcheckBox->isChecked())
    {
        seriesG->setVisible(true);
        ScatterseriesG1->setVisible(true);
    }
    else
    {
        seriesG->setVisible(false);
        ScatterseriesG1->setVisible(false);
    }
    if(BcheckBox->isChecked())
    {
        seriesB->setVisible(true);
        ScatterseriesB1->setVisible(true);
    }
    else
    {
        seriesB->setVisible(false);
        ScatterseriesB1->setVisible(false);
    }
}

void HistDialog::dataShow(const QPointF& point, bool state)
{
    qDebug() << point;
    if (state)
    {
        m_valueLabel->setText(QString::asprintf("%1.0f , %1.0f", point.x(), point.y()));
        QPoint curPos = mapFromGlobal(QCursor::pos());
        m_valueLabel->move(curPos.x() - m_valueLabel->width(), curPos.y() - 10);
        m_valueLabel->show();//显示
    }
    else
    {
        m_valueLabel->hide();//隐藏
    }
}
#ifndef HISTDIALOG_H
#define HISTDIALOG_H

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include "common.h"
class HistDialog : public QDialog
{
    Q_OBJECT

public:
    explicit HistDialog(cv::Mat& mat, QWidget *parent = 0);
    ~HistDialog();
    void imageHist(int chn, int N);
private:
    QChartView* area;
    QLineSeries* seriesR;
    QLineSeries* seriesG;
    QLineSeries* seriesB;
    QScatterSeries *ScatterseriesR1;
    QScatterSeries *ScatterseriesG1;
    QScatterSeries *ScatterseriesB1;

    QChart* chart;
    QCheckBox *RcheckBox;
    QCheckBox *GcheckBox;
    QCheckBox *BcheckBox;
    QLabel* m_valueLabel;
    QGridLayout *mainLayout;
    cv::Mat sourceImg;
    cv::MatND RChannelhist;
    cv::MatND GChannelhist;
    cv::MatND BChannelhist;
    double maxVal;
public slots:
    void imhist(int i);
    void dataShow(const QPointF & point, bool state);
};

#endif // HISTDIALOG_H

你可能感兴趣的:(opencv,Qt,图像处理,opencv,QChart)