文章目录

  • 前言
  • 一、函数介绍
  • 1 split
  • 2 calcHist
  • 3 normalize
  • 二、演示
  • 1、GUI
  • 2、实现代码
  • 总结



前言

越来越多的开发人员选择基于开源的Qt框架与OpenCV来实现界面和算法,其原因不单单是无版权问题,更多是两个社区的发展蓬勃,可用来学习的资料与例程特别丰富。以下是关于利用Qt构建GUI并使用OpenCV中的split/calcHist/normalize函数进行直方图计算。
软件版本:Qt-5.12.0/OpenCV-4.5.3
平台:Windows10/11–64


一、函数介绍

1 split

cv::split(const Mat& src, Mat *mvBegin)
cv::split(InputArray m, OutputArrayOfArrays mv);
参数解释:
src/m:要进行分离的图像矩阵;
mvBegin:Mat数组的首地址;
mv:vector对象;

2 calcHist

cv::calcHist(const Mat* images, int nimages, const int* channels, InputArray mask, OutputArray hist, int dims, const int* histSize, const float** ranges, bool uniform=true, bool accumulate = false )

cv::calcHist(const Mat* images, int nimages, const int* channels, InputArray mask, SparseMat& hist, int dims, const int* histSize, const float** ranges, bool uniform = true, bool accumulate= false )

参数解释:
images:源图像数组,它们有同样的位深CV_8U或 CV_32F ,同样的尺寸;图像阵列中的每一个图像都可以有任意多个通道;
nimages:源图像的数目;
channels:维度通道序列,第一幅图像的通道标号从0~image[0].channels( )-1。Image[0]表示图像数组中的第一幅图像,channels()表示该图像的通道数量。同理,图像阵列中的第二幅图像,通道标号从image[0].channerls( )开始,到image[1].channels( )-1为止;第三、四幅图像的通道标号顺序依此类推;也就是说图像阵列中的所有图像的通道根据图像排列顺序,排成一个通道队列;
mask: 可选择的mask。如果该矩阵不空的话,它必须是一个8-bit的矩阵,与images[i]同尺寸。在图像中,只有被mask覆盖的区域的像素才参与直方图统计。如果这个参数想用默认值,输入Mat()就可以了;
hist:输出直方图, 它是一个稠密或稀疏矩阵,具有dims个维度;
dims :直方图的维度,一定是正值, CV_MAX_DIMS(当前OpenCV版本是32个);
histSize:数组,即histSize[i]表示第i个维度上bin的个数;这里的维度可以理解为通道;
ranges: 当uniform=true时,ranges是多个二元数组组成的数组;当uniform=false时,ranges是多元数组组成的数组。当在每个维度(或通道)上每个直方条等宽时,即uniform=true时,灰度值的有效统计范围的下界用L0表示,上界用UhistSize[i]-1表示,角标中的i表示第i个维度(或通道),上下界值可以表示为hrange[i]={ L0, UhistSize[i]-1}, 在统计时, L0和UhistSize[i]-1不在统计范围内。而ranges={ hrange[0], hrange[1], …… , hrange[dims]}。ranges的元素个数由参数dims决定。其中,L0表示在该通道上第0个直方条(bin)的下边界,UhistSize[i]-1表示最后一个直方条histSize[i]-1的上边界。在该维度上直方条的个数为histSize[i],如hrange[0]={ L0, UhistSize[0]},hrange[1]={ L1, UhistSize[1]}, hrange[2]={ L2, UhistSize[2]}, …… , hrange[dims]={ L0, UhistSize[0]}。当uniform=false时,ranges中的每个元素ranges[i]都是一个多元数组,元素个数为histSize[i]+1,它们分别是:L0 , U0=L1, U1= L2, …… ,UhistSize[i]-2 , LhistSize[i]-1 , UhistSize[i]-1 。所以,ranges[i]={ L0 , L1, L2, …… , LhistSize[i]-1 ,UhistSize[i]-1};
uniform:标识,用于说明直方条bin是否是均匀等宽的;
accumulate: 累积标识。如果该项设置,当直方图重新分配时,直方图在开始清零。这个特征可以使你通过几幅图像,累积计算一个简单的直方图,或者及时更新直方图;

函数calcHist可以计算一幅或多幅图像的直方图。在元组中增量一个直方图的时候,就是从输入图像组中的原位置提取一幅图像,并计算出它的直方图,并添加到元组中。当参数dims>1时,输出矩阵Hist是二维矩阵。

3 normalize

cv::normalize(InputArry src, InputOutputArray dst, double alpha=1, double beta=0, int norm_type=NORM_L2, int dtype=-1, InputArray mark=noArry())
参数解释:
src:输入数组;
**dst **:输出数组,数组的大小和原数组一致;
alpha:1-用来规范值,2-规范范围,并且是下限;
beta:只用来规范范围并且是上限;
**norm_type **: 归一化选择的数学公式类型;
**dtype **:当为负,输出的宽高通道数都等于输入,当为正,输出只在深度与输入不同,不同的地方由dtype决定;
mark:掩码,选择感兴趣区域,选定后只能对该区域进行操作;

函数作用归一化数据,该函数分为范围归一化与数据值归一化,经常应用在将数值限定在一个范围,以便使用同一套阈值参数的情况。

二、演示

1、GUI

【Qt&OpenCV 直方图计算 split/calcHist/normalize】_数组


如上图创建Histgram的功能按钮QPushButton, 直方图展示在Histgram的窗口中。

2、实现代码

histBtn的clicked()槽函数实现如下:

void MainWindow::on_histBtn_clicked()
{

    std::size_t numView = ui->tabWidget->currentIndex() % 4;
    if (dispMat[numView]->empty())
    {
        outputInfo(2, tr("Please make sure the Mat exist!"));
    }

    std::vector<cv::Mat> rgb_planes;

    if (dispMat[numView]->channels() == 3)			
    {
        cv::split(*dispMat[numView], rgb_planes);		// split函数,分离通道;
    }
    else
    {
        rgb_planes.push_back(*dispMat[numView]);
        rgb_planes.push_back(*dispMat[numView]);
        rgb_planes.push_back(*dispMat[numView]);
    }

    outputInfo(1, "Channels: " + QString::number(dispMat[numView]->channels()));

    int histSize = 255;
    float range[] = {0, 255};
    const float* histRange = {range};
    bool uniform = true;
    bool accumulate = false;

    cv::Mat r_hist, g_hist, b_hist;

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

    int hist_w = 512;
    int hist_h = 800;
    cv::Mat tmpMat(hist_w, hist_h, CV_8UC3, cv::Scalar(0, 0, 0));			// 准备画布

    /*
    cv::normalize(r_hist, r_hist, 0, tmpMat.rows, cv::NORM_MINMAX, \		// 归一化
                  -1, cv::Mat());
    cv::normalize(g_hist, g_hist, 0, tmpMat.rows, cv::NORM_MINMAX, \
                  -1, cv::Mat());
    cv::normalize(b_hist, b_hist, 0, tmpMat.rows, cv::NORM_MINMAX, \
                  -1, cv::Mat());

    int bin_w = cvRound(static_cast<double> (hist_w/histSize));
    for (int i = 1; i < histSize; i++)
    {
        cv::line(tmpMat, cv::Point(bin_w*(i-1), hist_h - cvRound(r_hist.at<float>(i-1))),
                         cv::Point(bin_w*(i), hist_h - cvRound(r_hist.at<float>(i))),
                         cv::Scalar(0, 0, 255), 2, 8, 0);
        cv::line(tmpMat, cv::Point(bin_w*(i-1), hist_h - cvRound(g_hist.at<float>(i-1))),
                         cv::Point(bin_w*(i), hist_h - cvRound(g_hist.at<float>(i))),
                         cv::Scalar(0, 0, 255), 2, 8, 0);
        cv::line(tmpMat, cv::Point(bin_w*(i-1), hist_h - cvRound(b_hist.at<float>(i-1))),
                         cv::Point(bin_w*(i), hist_h - cvRound(b_hist.at<float>(i))),
                         cv::Scalar(0, 0, 255), 2, 8, 0);
    }
    */
    double rMax, gMax, bMax;
    cv::minMaxLoc(r_hist, nullptr, &rMax, nullptr, nullptr);
    cv::minMaxLoc(g_hist, nullptr, &gMax, nullptr, nullptr);
    cv::minMaxLoc(b_hist, nullptr, &bMax, nullptr, nullptr);

    double rBin_w = static_cast<double>(tmpMat.cols / histSize);
    double rBin_h = static_cast<double>(tmpMat.rows / rMax);
    double gBin_w = static_cast<double>(tmpMat.cols / histSize);
    double gBin_h = static_cast<double>(tmpMat.rows / gMax);
    double bBin_w = static_cast<double>(tmpMat.cols / histSize);
    double bBin_h = static_cast<double>(tmpMat.rows / bMax);

    for (int i = 1; i < histSize; i++)
    {
        cv::Point rP_0 = cv::Point(static_cast<int>(i*rBin_w), tmpMat.rows);
        int rVal = static_cast<int>(r_hist.at<float>(i));
        cv::Point rP_1 = cv::Point(static_cast<int>((i + 1)*rBin_w), \
                                   static_cast<int>(tmpMat.rows - rVal*rBin_h));
        cv::rectangle(tmpMat, rP_0, rP_1, cv::Scalar(0, 0, 255), 2, 8, 0);

        cv::Point gP_0 = cv::Point(static_cast<int>(i*gBin_w), tmpMat.rows);
        int gVal = static_cast<int>(g_hist.at<float>(i));
        cv::Point gP_1 = cv::Point(static_cast<int>((i + 1)*gBin_w), \
                                   static_cast<int>(tmpMat.rows - gVal*gBin_h));
        cv::rectangle(tmpMat, gP_0, gP_1, cv::Scalar(0, 255, 0), 2, 8, 0);

        cv::Point bP_0 = cv::Point(static_cast<int>(i*bBin_w), tmpMat.rows);
        int bVal = static_cast<int>(b_hist.at<float>(i));
        cv::Point bP_1 = cv::Point(static_cast<int>((i + 1)*bBin_w), \
                                   static_cast<int>(tmpMat.rows - bVal*bBin_h));
        cv::rectangle(tmpMat, bP_0, bP_1, cv::Scalar(255, 0, 0), 2, 8, 0);
    }

    char string[12];
    int mark = 0;
    for (int i = 1; mark < static_cast<int>(rMax); i++)
    {
        mark = static_cast<int>(i * rMax / 20);
        itoa(mark, string, 10);
        cv::putText(tmpMat, string, cv::Point(0, static_cast<int>(tmpMat.rows - \
                                              mark * rBin_h)), 1, 1, \
                                              cv::Scalar(0, 255, 255));
    }
    mark = 0;
    for (int i = 1; mark < 256; i++)
    {
        mark = i * 20;
        itoa(mark, string, 10);
        cv::putText(tmpMat, string, cv::Point(mark * (tmpMat.cols / 256),
                                    tmpMat.rows), 1, 1, cv::Scalar(0, 255, 255));
    }

    *dispMat[3] = tmpMat.clone();
    cvtMatPixmap(dispMat, dispPixmap, 3);
    outputInfo(1, tr("Histogram Action done."));

    double minVal, maxVal;
    cv::Point minLoc, maxLoc;

    if (dispMat[numView]->channels() == 3)
    {
        cv::cvtColor(*dispMat[numView], tmpMat, cv::COLOR_RGB2GRAY);
    }
    else
    {
        tmpMat = *dispMat[numView];
    }
    cv::minMaxLoc(tmpMat, &minVal, &maxVal, &minLoc, &maxLoc);
    QString minMaxLocVal = "minVal: " + QString::number(minVal) + \
                           " maxVal: " + QString::number(maxVal);
    std::stringstream tmpStream;
    std::streambuf* coutBuf = std::cout.rdbuf();
    std::cout.rdbuf(tmpStream.rdbuf());
    std::cout << " minLoc: " << minLoc << " maxLoc: " << maxLoc << std::endl;
    std::string minMaxLocString(tmpStream.str());
    std::cout.rdbuf(coutBuf);
    QString infoMinMaxLoc = minMaxLocVal + QString::fromStdString(minMaxLocString);

    outputInfo(1, infoMinMaxLoc);
}

总结

以上是关于利用Qt进行GUI构建并使用OpenCV中的split/calcHist/normalized函数进行图像缩放的介绍。