- 操作系统:手机安装 Termux ,Termux 内安装 ubuntu
- Termux 0.119.0-beta.3
- 内嵌 ubuntu 24.04
- 编译器:GNU g++ 14.2.0
- 编辑器:Emacs 29.3
- OpenCV:4.13.0
目标
- 使用 OpenCV 函数 cv::split 将图像划分为其对应的平/层面
- 使用 OpenCV 函数 cv::calcHist 计算图像数组的直方图
- 使用函数 cv::normalize 规范化数组
理论
再谈什么是直方图?
- 直方图是收集的数据计数,组织到一组预定义的箱中
- 当我们说数据时,我们并没有将其限制为强度值(正如我们在之前的教程直方图均衡中看到的那样)。收集的数据可以是您认为对描述图像有用的任何特征。
- 让我们看一个例子。想象一下,矩阵包含图像信息(即范围内的强度(0-255)):
- 如果我们想以有组织的方式计算这些数据会发生什么?由于我们知道这种情况下的信息值范围是 256 个值,因此我们可以将范围分割为子部分(称为箱),如下所示:
[0,255]=[0,15]∪[16,31]∪....∪[240,255]range=bin1∪bin2∪....∪binn=15 \begin{array}{l} [0, 255] = { [0, 15] \cup [16, 31] \cup ....\cup [240,255] } \\ range = { bin_{1} \cup bin_{2} \cup ....\cup bin_{n = 15} } \end{array} [0,255]=[0,15]∪[16,31]∪....∪[240,255]range=bin1∪bin2∪....∪binn=15 - 我们可以计算落在每个范围内的像素数 binibin_ibini 。将其应用于上面的示例,我们得到下图(轴 x 代表箱,轴 y 代表每个箱中的像素数)
- 这只是直方图如何工作以及为什么它有用的一个简单示例。直方图不仅可以计算颜色强度,还可以计算我们想要测量的任何图像特征(即渐变、方向等)
- 让我们确定直方图的某些部分:
- dims:要收集数据的参数数。在我们的示例中,dims = 1,因为我们只计算每个像素的强度值(在灰度图像中)
- bins:它是每个 dim 中的细分数。在我们的示例中,bins = 16
- range:要测量的值的限制。在本例中:范围 = [0,255]
OpenCV 为您提供什么?
出于简单的目的,OpenCV 实现了函数 cv::calcHist ,它计算一组数组(通常是图像或图像平面)的直方图。它最多可以在 32 个尺寸下运行。我们将在下面的代码中看到它
源代码
#include "opencv2/highgui.hpp"
#include "opencv2/imgcodecs.hpp"
#include "opencv2/imgproc.hpp"
#include <iostream>
using namespace std;
using namespace cv;
int main(int argc, char** argv)
{
CommandLineParser parser( argc, argv, "{@input | lena.jpg | input image}" );
Mat src = imread( samples::findFile( parser.get<String>( "@input" ) ), IMREAD_COLOR );
if( src.empty() )
{
return EXIT_FAILURE;
}
vector<Mat> bgr_planes;
split( src, bgr_planes );
int histSize = 256;
float range[] = { 0, 256 }; //the upper boundary is exclusive
const float* histRange[] = { range };
bool uniform = true, 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, hist_h = 400;
int bin_w = cvRound( (double) hist_w/histSize );
Mat histImage( hist_h, hist_w, CV_8UC3, Scalar( 0,0,0) );
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<float>(i-1)) ),
Point( bin_w*(i), hist_h - cvRound(b_hist.at<float>(i)) ),
Scalar( 255, 0, 0), 2, 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), 2, 8, 0 );
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( 0, 0, 255), 2, 8, 0 );
}
imshow("Source image", src );
imshow("calcHist Demo", histImage );
waitKey();
return EXIT_SUCCESS;
}
解释说明
- 加载图像
CommandLineParser parser( argc, argv, "{@input | lena.jpg | input image}" ); Mat src = imread( samples::findFile( parser.get<String>( "@input" ) ), IMREAD_COLOR ); if( src.empty() ) { return EXIT_FAILURE; }
- 将源图像的三个 R、G 和 B 平面分开。为此,我们使用 OpenCV 函数 cv::split :
vector<Mat> bgr_planes; split( src, bgr_planes );
- 现在我们准备好开始为每个平面配置直方图。由于我们正在使用 B、G 和 R 平面,因此我们知道我们的值将在区间内
- 确定箱数 (5, 10…):
int histSize = 256;
- 设置值范围
float range[] = { 0, 256 }; //the upper boundary is exclusive
const float* histRange[] = { range };
- 我们希望我们的箱具有相同的大小(统一),并在开始时清除直方图,因此:
bool uniform = true, accumulate = false;
- 我们继续使用 OpenCV 函数 cv::calcHist 计算直方图:
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, hist_h = 400; int bin_w = cvRound( (double) hist_w/histSize ); Mat histImage( hist_h, hist_w, CV_8UC3, Scalar( 0,0,0) );
- 请注意,在绘制之前,我们首先对直方图进行 cv::normalize ,使其值落在输入的参数指示的范围内:
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() );
- 注意,要访问 bin
for( int i = 1; i < histSize; i++ ) { 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( 255, 0, 0), 2, 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), 2, 8, 0 ); 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( 0, 0, 255), 2, 8, 0 ); }
- 最后,我们显示直方图并等待用户退出:
imshow("Source image", src ); imshow("calcHist Demo", histImage ); waitKey();
结果
- 源图像
- 生成的直方图