OpenCV官方教程-Java-翻译矫正01-什么是直方图?

什么是直方图?

  • 直方图是收集的数据计数,组织成一组预定义的bin

  • 当我们说数据时,我们并没有将其限制为强度值(正如我们在前面的教程直方图均衡中看到的)。收集的数据可以是您认为对描述图像有用的任何特征。

  • 让我们看一个例子。想象一个矩阵包含图像的信息(即范围内的强度0 - 255):

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-htmquDVg-1649666719963)(%E5%A4%AA%E5%88%9D%E7%B2%BE%E7%81%B5l-%E8%AE%BA%E6%96%87%E8%AE%B0%E5%BD%95-%E5%8F%82%E8%80%83.assets/Histogram_Calculation_Theory_Hist0.jpg)]

  • 如果我们想以有组织的方式统计这些数据会发生什么?由于我们知道这种情况下的信息值范围是 256 个值,我们可以将我们的范围划分为子部分(称为bins),例如:

    [0,255]=[0,15]∪[16,31]∪…∪[240,255]range=bin1∪bin2∪…∪binn=15

    我们可以计算落在每个范围内的像素数我_n一世. 将其应用于上面的示例,我们得到下面的图像(轴 x 表示 bin,轴 y 表示每个 bin 中的像素数)。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6t6kFQ8g-1649666719965)(%E5%A4%AA%E5%88%9D%E7%B2%BE%E7%81%B5l-%E8%AE%BA%E6%96%87%E8%AE%B0%E5%BD%95-%E5%8F%82%E8%80%83.assets/Histogram_Calculation_Theory_Hist1.jpg)]

  • 这只是直方图如何工作以及为什么有用的一个简单示例。直方图不仅可以记录颜色强度,还可以记录我们想要测量的任何图像特征(即梯度、方向等)。

  • 让我们识别直方图的某些部分:

    1. dims:您要收集数据的参数数量。在我们的示例中,dims = 1因为我们只计算每个像素的强度值(在灰度图像中)。
    2. bins:它是每个dim中的**细分数。**在我们的示例中,bin = 16
    3. range:要测量的值的限制。在这种情况下:范围 = [0,255]
  • 如果你想计算两个特征怎么办?在这种情况下,您生成的直方图将是一个 3D 图(其中 x 和 y 将是binx 和 biny是的对于每个特征,z 将是每个组合的计数((binx,biny)). 这同样适用于更多功能(当然它会变得更棘手)。

OpenCV 为您提供什么

出于简单的目的,OpenCV 实现了函数cv::calcHist,它计算一组数组(通常是图像或图像平面)的直方图。它可以操作多达 32 个维度。我们将在下面的代码中看到它!

代码 C++爪哇Python

  • 这个程序有什么作用?

  • 可下载代码:点击这里

  • 代码一览:

  • 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);
        }
    }
    

解释Java

  • 加载源图像
 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);
        }
  • 在其三个 R、G 和 B 平面中分离源图像。为此,我们使用 OpenCV 函数cv::split
 List<Mat> bgrPlanes = new ArrayList<>();
 Core.split(src, bgrPlanes);

我们的输入是要分割的图像(这种情况下是三个通道),输出是 Mat 的向量)

  • 现在我们准备开始为每个平面配置直方图。由于我们正在使用 B、G 和 R 平面,我们知道我们的值将在区间内变化[ 0 , 255 ]
  • 确定 bin 的数量(5、10…):

int histSize = 256;

  • 设置值的范围(如我们所说,介于 0 和 255 之间)
float[] range = {0, 256}; //上边界是独占的
MatOfFloat histRange = new MatOfFloat(range);
  • 我们希望我们的 bin 具有相同的大小(统一)并在开始时清除直方图,因此:

​ boolean accumulate = false;

  • 我们继续使用 OpenCV 函数cv::calcHist计算直方图:
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++ 代码

    ):

    • **&bgr_planes[0]😗*源数组
    • 1:源数组的数量(在这种情况下,我们使用 1。我们也可以在此处输入数组列表)
    • 0 :要测量的通道(*暗淡)。*在这种情况下,它只是强度(每个阵列都是单通道),所以我们只写 0。
    • Mat():要在源数组上使用的掩码(零表示要忽略的像素)。如果未定义,则不使用
    • b_hist:将存储直方图的 Mat 对象
    • 1:直方图维数。
    • **histSize:**每个使用的维度的 bin 数量
    • **histRange:**每个维度要测量的值的范围
    • uniformaccumulate:bin 大小相同,直方图在开始时被清除。
  • 创建图像以显示直方图:

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++ 代码

    ):

    • **b_hist:**输入数组
    • **b_hist:**输出归一化数组(可以相同)
    • 0histImage.rows:对于这个例子,它们是标准化r_hist值的下限和上限
    • **NORM_MINMAX:**表示归一化类型的参数(如上所述,它调整之前设置的两个限制之间的值)
    • **-1:**表示输出的归一化数组将与输入的类型相同
    • **Mat():**可选掩码
  • 观察以访问 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

结果

  1. 使用如下图所示的图像作为输入参数:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dkHedciR-1649666719966)(%E5%A4%AA%E5%88%9D%E7%B2%BE%E7%81%B5l-%E8%AE%BA%E6%96%87%E8%AE%B0%E5%BD%95-%E5%8F%82%E8%80%83.assets/Histogram_Calculation_Original_Image.jpg)]

  1. 生成以下直方图:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-U1YgYwXk-1649666719967)(%E5%A4%AA%E5%88%9D%E7%B2%BE%E7%81%B5l-%E8%AE%BA%E6%96%87%E8%AE%B0%E5%BD%95-%E5%8F%82%E8%80%83.assets/Histogram_Calculation_Result.jpg)]


官网地址:
https://docs.opencv.org/3.4/d8/dbc/tutorial_histogram_calculation.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值