直方图是收集的数据计数,组织成一组预定义的bin
当我们说数据时,我们并没有将其限制为强度值(正如我们在前面的教程直方图均衡中看到的)。收集的数据可以是您认为对描述图像有用的任何特征。
让我们看一个例子。想象一个矩阵包含图像的信息(即范围内的强度0 - 255):
如果我们想以有组织的方式统计这些数据会发生什么?由于我们知道这种情况下的信息值范围是 256 个值,我们可以将我们的范围划分为子部分(称为bins),例如:
[0,255]=[0,15]∪[16,31]∪…∪[240,255]range=bin1∪bin2∪…∪binn=15
我们可以计算落在每个范围内的像素数我_n一世. 将其应用于上面的示例,我们得到下面的图像(轴 x 表示 bin,轴 y 表示每个 bin 中的像素数)。
这只是直方图如何工作以及为什么有用的一个简单示例。直方图不仅可以记录颜色强度,还可以记录我们想要测量的任何图像特征(即梯度、方向等)。
让我们识别直方图的某些部分:
如果你想计算两个特征怎么办?在这种情况下,您生成的直方图将是一个 3D 图(其中 x 和 y 将是binx 和 biny是的对于每个特征,z 将是每个组合的计数((binx,biny)). 这同样适用于更多功能(当然它会变得更棘手)。
出于简单的目的,OpenCV 实现了函数cv::calcHist,它计算一组数组(通常是图像或图像平面)的直方图。它可以操作多达 32 个维度。我们将在下面的代码中看到它!
这个程序有什么作用?
可下载代码:点击这里
代码一览:
import java.util.ArrayList;
import java.util.List;
import org.opencv.core.Core;
import org.opencv.core.CvType;
import org.opencv.core.Mat;
import org.opencv.core.MatOfFloat;
import org.opencv.core.MatOfInt;
import org.opencv.core.Point;
import org.opencv.core.Scalar;
import org.opencv.highgui.HighGui;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.imgproc.Imgproc;
class CalcHist {
public void run(String[] args) {
String filename = args.length > 0 ? args[0] : "../data/lena.jpg";
Mat src = Imgcodecs.imread(filename);
if (src.empty()) {
System.err.println("Cannot read image: " + filename);
System.exit(0);
}
List<Mat> bgrPlanes = new ArrayList<>();
Core.split(src, bgrPlanes);
int histSize = 256;
float[] range = {0, 256}; //the upper boundary is exclusive
MatOfFloat histRange = new MatOfFloat(range);
boolean accumulate = false;
Mat bHist = new Mat(), gHist = new Mat(), rHist = new Mat();
Imgproc.calcHist(bgrPlanes, new MatOfInt(0), new Mat(), bHist, new MatOfInt(histSize), histRange, accumulate);
Imgproc.calcHist(bgrPlanes, new MatOfInt(1), new Mat(), gHist, new MatOfInt(histSize), histRange, accumulate);
Imgproc.calcHist(bgrPlanes, new MatOfInt(2), new Mat(), rHist, new MatOfInt(histSize), histRange, accumulate);
int histW = 512, histH = 400;
int binW = (int) Math.round((double) histW / histSize);
Mat histImage = new Mat( histH, histW, CvType.CV_8UC3, new Scalar( 0,0,0) );
Core.normalize(bHist, bHist, 0, histImage.rows(), Core.NORM_MINMAX);
Core.normalize(gHist, gHist, 0, histImage.rows(), Core.NORM_MINMAX);
Core.normalize(rHist, rHist, 0, histImage.rows(), Core.NORM_MINMAX);
float[] bHistData = new float[(int) (bHist.total() * bHist.channels())];
bHist.get(0, 0, bHistData);
float[] gHistData = new float[(int) (gHist.total() * gHist.channels())];
gHist.get(0, 0, gHistData);
float[] rHistData = new float[(int) (rHist.total() * rHist.channels())];
rHist.get(0, 0, rHistData);
for( int i = 1; i < histSize; i++ ) {
Imgproc.line(histImage, new Point(binW * (i - 1), histH - Math.round(bHistData[i - 1])),
new Point(binW * (i), histH - Math.round(bHistData[i])), new Scalar(255, 0, 0), 2);
Imgproc.line(histImage, new Point(binW * (i - 1), histH - Math.round(gHistData[i - 1])),
new Point(binW * (i), histH - Math.round(gHistData[i])), new Scalar(0, 255, 0), 2);
Imgproc.line(histImage, new Point(binW * (i - 1), histH - Math.round(rHistData[i - 1])),
new Point(binW * (i), histH - Math.round(rHistData[i])), new Scalar(0, 0, 255), 2);
}
HighGui.imshow( "Source image", src );
HighGui.imshow( "calcHist Demo", histImage );
HighGui.waitKey(0);
System.exit(0);
}
}
public class CalcHistDemo {
public static void main(String[] args) {
// Load the native OpenCV library
System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
new CalcHist().run(args);
}
}
String filename = args.length > 0 ? args[0] : "../data/lena.jpg";
Mat src = Imgcodecs.imread(filename);
if (src.empty()) {
System.err.println("Cannot read image: " + filename);
System.exit(0);
}
List<Mat> bgrPlanes = new ArrayList<>();
Core.split(src, bgrPlanes);
我们的输入是要分割的图像(这种情况下是三个通道),输出是 Mat 的向量)
int histSize = 256;
float[] range = {0, 256}; //上边界是独占的
MatOfFloat histRange = new MatOfFloat(range);
boolean accumulate = false;
Mat bHist = new Mat(), gHist = new Mat(), rHist = new Mat();
Imgproc.calcHist(bgrPlanes, new MatOfInt(0), new Mat(), bHist, new MatOfInt(histSize), histRange, accumulate);
Imgproc.calcHist(bgrPlanes, new MatOfInt(1), new Mat(), gHist, new MatOfInt(histSize), histRange, accumulate);
Imgproc.calcHist(bgrPlanes, new MatOfInt(2), new Mat(), rHist, new MatOfInt(histSize), histRange, accumulate);
其中参数是(
C++ 代码
):
创建图像以显示直方图:
int histW = 512, histH = 400;
int binW = (int) Math.round((double) histW / histSize);
Mat histImage = new Mat( histH, histW, CvType.CV_8UC3, new Scalar( 0,0,0) );
请注意,在绘制之前,我们首先对直方图进行cv::normalize,使其值落在输入参数指示的范围内:
Core.normalize(bHist, bHist, 0, histImage.rows(), Core.NORM_MINMAX);
Core.normalize(gHist, gHist, 0, histImage.rows(), Core.NORM_MINMAX);
Core.normalize(rHist, rHist, 0, histImage.rows(), Core.NORM_MINMAX);
此函数接收这些参数(
C++ 代码
):
观察以访问 bin(在这种情况下,在此 1D 直方图中):
float[] bHistData = new float[(int) (bHist.total() * bHist.channels())];
bHist.get(0, 0, bHistData);
float[] gHistData = new float[(int) (gHist.total() * gHist.channels())];
gHist.get(0, 0, gHistData);
float[] rHistData = new float[(int) (rHist.total() * rHist.channels())];
rHist.get(0, 0, rHistData);
for( int i = 1; i < histSize; i++ ) {
Imgproc.line(histImage, new Point(binW * (i - 1), histH - Math.round(bHistData[i - 1])),
new Point(binW * (i), histH - Math.round(bHistData[i])), new Scalar(255, 0, 0), 2);
Imgproc.line(histImage, new Point(binW * (i - 1), histH - Math.round(gHistData[i - 1])),
new Point(binW * (i), histH - Math.round(gHistData[i])), new Scalar(0, 255, 0), 2);
Imgproc.line(histImage, new Point(binW * (i - 1), histH - Math.round(rHistData[i - 1])),
new Point(binW * (i), histH - Math.round(rHistData[i])), new Scalar(0, 0, 255), 2);
}
我们使用表达式(C++ 代码):
b_hist.at<float>(i)
在哪里一世表示维度。如果它是一个二维直方图,我们会使用类似的东西:
b_hist.at<float>( i, j )
HighGui.imshow( "Source image", src );
HighGui.imshow( "calcHist Demo", histImage );
HighGui.waitKey(0);
Result
官网地址:
https://docs.opencv.org/3.4/d8/dbc/tutorial_histogram_calculation.html