【OpenCV基础】第二十二课:直方图计算

cv::split,cv::merge,cv::calcHist,cv::normalize

Posted by x-jeff on August 2, 2021

本文为原创文章,未经本人允许,禁止转载。转载请注明出处。

1.相关API

图像直方图的相关概念:图像直方图

1.1.cv::split

1
2
3
4
void split(
	InputArray m, //输入图像
	OutputArrayOfArrays mv //输出的单通道图像数组
);

cv::split将多通道图像分离成多个单通道图像。例如:

相反的,cv::merge可用于将多个单通道图像合并为一个多通道图像:

1
2
3
4
void merge(
	InputArrayOfArrays mv, 
	OutputArray dst
);

1.2.cv::calcHist

1
2
3
4
5
6
7
8
9
10
11
12
void 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 
);

参数解释:

  1. const Mat* images:输入的图像或图像数组,它们的深度必须为CV_8UCV_16UCV_32F中的一类,尺寸必须相同。
  2. int nimages:第一个参数中存放了几张图像。
  3. const int* channels:需要统计的通道。第1个图像的通道从0到images[0].channels()-1,第2个图像的通道从images[0].channels()images[0].channels() + images[1].channels()-1,剩余的以此类推。比如输入包含2个图像,第1个图像共有三个通道(编号应为0,1,2),第2个图像共有一个通道(编号应为3),如果int channels[3] = {3, 2, 0},那么就表示是使用第2个图像的第一个通道(编号为3)和第1个图像的第三个通道(编号为2)以及第一个通道(编号为1)。
  4. InputArray mask:可选的掩码,如果不为空,则必须是8-bit数组,而且大小和原图像相同,非零位置为要计算的直方图区域。
  5. OutputArray hist:输出的直方图。
  6. int dims:直方图的维数。必须和参数7、参数8是对应的。
  7. const int* histSize:直方图每一维度上的数组个数(bin的个数)。
  8. const float** ranges:每一维的数据范围。例如灰度图像通常使用0~255。
  9. bool uniform:直方图是否均匀,即每一个竖条的宽度是否相等。
  10. bool accumulate:累积标识符,默认值为false。若为true,直方图再分配阶段不会清零。此功能主要是允许从多个阵列中计算单个直方图或者用于在特定的时间更新直方图。

此外,cv::calcHist还有另外两种重载方式,参数解释大同小异,不再赘述:

1
2
3
4
5
6
7
8
9
10
11
12
void 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
);
1
2
3
4
5
6
7
8
9
void calcHist( 
	InputArrayOfArrays images,
	const std::vector<int>& channels,
	InputArray mask, 
	OutputArray hist,
	const std::vector<int>& histSize,
	const std::vector<float>& ranges,
	bool accumulate = false 
);

举个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <opencv2/opencv.hpp>
#include <iostream>
#include <math.h>

using namespace std;
using namespace cv;

int main(int argc, char** argv) {
    Mat m1 = (Mat_<uchar>(3, 3) << 0, 1, 2, 0, 4, 5, 6, 7, 8);
    Mat m2 = (Mat_<uchar>(3, 3) << 0, 1, 2, 1, 4, 5, 6, 7, 8);
    Mat m3 = (Mat_<uchar>(3, 3) << 0, 1, 2, 2, 4, 5, 6, 7, 8);
    Mat m[] = { m1,m2,m3 };
    int histSize = 9;
    float range[] = { 0, 9 };
    const float* histRanges = { range };
    Mat hist;
    int ch[] = { 0 };//ch里放多个值时,只会默认使用第一个值
    calcHist(m, 3, ch, Mat(), hist, 1, &histSize, &histRanges, true, false);//第6个参数此处必须为1(即使第一个参数为彩色图像)
    for (int c = 0; c < hist.cols; c++){
        for (int r = 0; r < hist.rows; r++) {
            float it = hist.at<float>(r, c);//类型只能是float,试了int,char,uchar,double,short均报错
            cout << it << " ";
        }
        cout << endl;
    }
    return 0;
}
//输出为:
//2 1 1 0 1 1 1 1 1

这里再详细说下参数6(int dims)。该参数指的是直方图的维数,例如二维直方图:

举个简单的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
int main(int argc, char** argv) {
    Mat m1 = (Mat_<uchar>(3, 3) << 0, 1, 2, 0, 4, 5, 6, 7, 8);
    Mat m2 = (Mat_<uchar>(3, 3) << 0, 1, 2, 0, 4, 5, 6, 7, 8);
    Mat m3 = (Mat_<uchar>(3, 3) << 0, 1, 2, 2, 4, 5, 6, 7, 8);
    Mat m[] = { m1,m2,m3 };
    int histSize[] = { 9,9 };//和dim对应
    float range[] = { 0, 9 };
    const float* histRanges[] = { range,range };//和dim对应
    Mat hist;
    int ch[] = { 0,1 };
    calcHist(m, 3, ch, Mat(), hist, 2, histSize, histRanges, true, false);
    for (int c = 0; c < hist.cols; c++) {
        for (int r = 0; r < hist.rows; r++) {
            float it = hist.at<float>(r, c);
            cout << it << " ";
        }
        cout << endl;
    }
    return 0;
}
//输出为:
/*
2 0 0 0 0 0 0 0 0
0 1 0 0 0 0 0 0 0
0 0 1 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0
0 0 0 0 1 0 0 0 0
0 0 0 0 0 1 0 0 0
0 0 0 0 0 0 1 0 0
0 0 0 0 0 0 0 1 0
0 0 0 0 0 0 0 0 1
*/

可以看出输出为$9\times 9$的二维直方图,m1和m2会被配对:$(0,0),(1,1),(2,2),(0,0),(4,4),(5,5),(6,6),(7,7),(8,8)$,这些对子会被统计为二维直方图。

1.3.cv::normalize

在直方图可视化时通常会用到归一化。

1
2
3
4
5
6
7
8
9
void normalize( 
	InputArray src, 
	InputOutputArray dst, 
	double alpha = 1, 
	double beta = 0,
	int norm_type = NORM_L2, 
	int dtype = -1, 
	InputArray mask = noArray()
);

参数解释:

  1. InputArray src:归一化前的图像。
  2. InputOutputArray dst:归一化后的图像。
  3. double alpha
    • 当归一化方法为范数归一化(NORM_L1NORM_L2NORM_INF)时,通过scale和shift使得:$$\parallel dst \parallel _{L_p}=alpha,p=Inf,1 \ or \ 2$$
    • 当归一化方法为范围归一化(NORM_MINMAX)时:$alpha$为范围归一化的下边界。
  4. double beta:在范数归一化时无效,在范围归一化时为上边界。例如将单通道图像归一化到0~255之间:cv::normalize(src,dst,0,255,NORM_MINMAX,CV_8UC1);
  5. int norm_type:归一化方法。共有四种:
    • NORM_L1:$$dst(i,j)=\frac{src(i,j)}{\sum \lvert src(x,y) \rvert}$$$$\{2.0,8.0,10.0\} \to \{0.1,0.4,0.5\}$$
    • NORM_L2:$$dst(i,j)=\frac{src(i,j)}{\sqrt{\sum src(x,y)^2}}$$$$\{2.0,8.0,10.0\} \to \{0.15,0.62,0.77\}$$
    • NORM_INF:$$dst(i,j)=\frac{src(i,j)}{max \lvert src(x,y) \rvert}$$$$\{2.0,8.0,10.0\} \to \{0.2,0.8,1.0\}$$
    • NORM_MINMAX:$$dst(i,j)=\frac{[src(i,j)-min(src(x,y))]*(\lvert alpha-beta \rvert)}{max(src(x,y))-min(src(x,y))}+min(alpha,beta)$$$$\{2.0,8.0,10.0\} \to \{0.0,0.75,1.0\}$$
  6. int dtype:dst的位图深度。如果为负值,则表示dst和src图像类型相同。如果为正值,那么dst的通道数等于src的通道数,且dst的位图深度等于CV_MAT_DEPTH(dtype)
  7. InputArray mask:通过mask控制需要归一化的区域。

举个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <opencv2/opencv.hpp>
#include <iostream>
#include <math.h>

using namespace std;
using namespace cv;

int main(int argc, char** argv) {
    Mat src = (Mat_<double>(1, 3) << -2., 8., 10.);
    Mat dst(src.size(), src.depth());
    cv::normalize(src, dst, 1, 0, NORM_L1, -1, Mat());//语句1
    for (int i = 0; i < 3; i++) {
        double d1 = src.at<double>(0, i);
        double d2 = dst.at<double>(0, i);
        cout << d1 << "-->" << d2 << endl;
    }
    return 0;
}
1
2
3
-2-->-0.1
8-->0.4
10-->0.5
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
//修改语句1为:
cv::normalize(src, dst, 2, 0.1, NORM_L1, -1, Mat());//alpha负责缩放功能,beta在NORM_L1时没用
//输出为:
/*
-2-->-0.2
8-->0.8
10-->1
*/

//修改语句1为:
cv::normalize(src, dst, 2, 0.5, NORM_MINMAX, -1, Mat());
//输出为:
/*
-2-->0.5
8-->1.75
10-->2
*/

//修改语句1为:
cv::normalize(src, dst, -1, 2, NORM_MINMAX, -1, Mat());
//输出为:
/*
-2-->-1
8-->1.5
10-->2
*/

2.代码地址

  1. 直方图计算

3.参考资料

  1. 【OpenCV3】图像通道分离与合并——cv::split()与cv::merge()详解