文章目录
- 1 直方图的概念
- 2 直方图计算:calcHist
- 3 示例
1 直方图的概念
从数学上来说,图像直方图是描述图像的各个灰度级的统计特性,它是图像灰度值的函数,统计图像中各个灰度级出现的次数或频率。
直方图横坐标是像素值,范围从 0 到 255,纵坐标表示图像中该像素值的个数。
图像直方图由于其计算代价较小,且具有图像平移、旋转、缩放不变性等众多优点,广泛地应用于图像处理的各个领域,特别是灰度图像的阈值分割、基于颜色的图像检索以及图像分类。
- 直方图均衡化:是指通过某种灰度映射(如:非线性拉伸)使原始图像的直方图变换为在整个灰度范围内均匀分布;目的是增加像素灰度值的动态范围,增强图像整体对比度;
- 直方图规定化:就是要调整原始图像的直方图去逼近规定的目标直方图;有选择地增强某个灰度范围内的对比度或使图像灰度值满足某种特定的分布;
- 归一化直方图:各个灰度级出现的次数除以图像的像素总数,即得到各个灰度级出现的概率,从而得到归一化直方图;
2 直方图计算:calcHist
先看一个示例,下图表示一张灰度图的像素值矩阵,取值范围(0~255):
将数值范围 0~255 进一步的划分为16个子区段,称为箱子(Bin):
然后,统计每个bin范围内的像素数,可得到下面的图像(x轴代表箱子,y轴代表每个箱子中的像素数):
这就是直方图计算的基本过程,当然直方图计算不仅可以计算图像的颜色像素值,它可以计算任何可以测量的图像特征,比如梯度、方向等等。
直方图计算中几个重要的概念:
- 维度(dims):就是要统计的图像特征的个数,比如示例中,dims = 1,统计的特征是像素灰度等级;
- 箱子(bins):将维度的取值范围划分为多少个子区段;
- 取值范围(range):测量值的取值范围;
为了计算直方图,OpenCV 实现了 calcHist 函数,该函数计算一组数组(通常是图像或图像平面)的直方图。它最多可以操作32个维度(dims)。
3 示例
方法定义:
public static void calcHist(List<Mat> images, MatOfInt channels, Mat mask, Mat hist, MatOfInt histSize, MatOfFloat ranges, boolean accumulate);
public static void calcHist(List<Mat> images, MatOfInt channels, Mat mask, Mat hist, MatOfInt histSize, MatOfFloat ranges);
参数说明:
- images: 图像各通道列表
- channels : 目标通道序号
- mask: 要测量的通道(dim)
- hist: 直方图计算记过
- histSize: bin 大小
- histRange: 取值范围
- accumulate: 箱子大小相同,柱状图在开始时被清除。
示例代码:
import java.util.ArrayList;
import java.util.List;
import org.bytedeco.javacpp.Loader;
import org.junit.Test;
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;
public class HistTest {
static {
Loader.load(org.bytedeco.opencv.opencv_java.class);
}
@Test
public void run() {
Mat src = Imgcodecs.imread("images/dog.PNG");
List<Mat> bgrPlanes = new ArrayList<>();
// 将多通道图像拆分为独立的三张图;
Core.split(src, bgrPlanes);
int histSize = 256;
float[] range = {0, 256}; // 0 <= x < 256
MatOfFloat histRange = new MatOfFloat(range); // 定义直方图的统计值范围;
// 声明三个 Mat 对象,用来存储生成的直方图信息;
Mat bHist = new Mat(), gHist = new Mat(), rHist = new Mat();
Imgproc.calcHist(bgrPlanes, new MatOfInt(0), new Mat(), bHist, new MatOfInt(histSize), histRange);
Imgproc.calcHist(bgrPlanes, new MatOfInt(1), new Mat(), gHist, new MatOfInt(histSize), histRange);
Imgproc.calcHist(bgrPlanes, new MatOfInt(2), new Mat(), rHist, new MatOfInt(histSize), histRange);
// 接下来,将直方图数据进行图像化处理,并显示到窗口中;
int histW = 512, histH = 400;
int binW = (int) Math.round((double) histW / histSize);
// 图像尺寸 512x400, 三通道
Mat histImage = new Mat( histH, histW, CvType.CV_8UC3, new Scalar( 0,0,0) );
// 将直方图数据归一化到 [0, 400] 区间,这样能得到比较好的显示效果;
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[] 数组中,方便处理;
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);
}
}
运行结果: