Couting the Pixels with Histograms

本文介绍了图像处理中直方图的基本概念与应用方法,包括直方图计算、归一化、增强等关键技术,并通过实例演示如何使用OpenCV实现直方图均衡化及对比度拉伸。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

The distribution of pixels value across the image constitutes an important characteristic of this image. that's the concept discript as histograms.A histogram is a simple table that gives the numbeer of pixes that have a give value in an images or sometimes a set of image. 也就是一张图片中每个颜色的像素有多少的统计表。 比如gray-level. will have 256 entries (or bins),Bin 0 give the number of pixel having value 0, bin 1 number of pixes have value1 and so on. Obvisously, if you sum all of these bins of a histogram, you should get the total number of pixels. Histograms can also be normalized such that sum of the bins equals 1, each bin give the percentage of pixels haveing specific value in the image.



Calculates a histogram of a set of arrays.

C++: voidcalcHist(const Mat*images, intnimages, const int*channels, InputArraymask, OutputArrayhist, intdims, const int*histSize, const float**ranges, booluniform=true, boolaccumulate=false )

C++: voidcalcHist(const Mat*images, intnimages, const int*channels, InputArraymask, SparseMat&hist, intdims, const int*histSize, const float**ranges, booluniform=true, boolaccumulate=false )
  • images – Source arrays. They all should have the same depth,CV_8U orCV_32F , and the same size. Each of them can have an arbitrary number of channels.
  • nimages – Number of source images.

     上面两个参数很好理解因为数组参数我们是不知道size 的,所以传递 images 要加上一个 nimages 说明有多少个image, *image++ till to *image[nimages-1]

  • channels – List of the dims channels used to compute the histogram. The first array channels are numerated from 0 toimages[0].channels()-1 , the second array channels are counted fromimages[0].channels() toimages[0].channels() + images[1].channels()-1, and so on.

      Mat 本身就有channels()函数可以获得自己有多少个channel的,所以这个参数是用来指示我们用那些channels 去计算histogram, ie int chnels[]={0,1}; means we calculate each image's 0th and 1st channel, if

int channels[]={1,2};will calculate channles[1] and channels[2]


  • mask – Optional mask. If the matrix is not empty,it must be an 8-bit array of the same size asimages[i] . The non-zero mask elements mark the array elements counted in the histogram.  (need to do a experiment to get understand it ,so let it pass first)


  • hist – Output histogram, which is a dense or sparsedims -dimensional array.
  • dims – Histogram dimensionality that must be positive and not greater thanCV_MAX_DIMS (equal to 32 in the current OpenCV version).只计算一个channel 就是 1D, 如果是2个channel就是2D ,

  

  • histSize – Array of histogram sizes in each dimension. 比如我们计算HSV H,S 两个channel,H channel 最大去到30也就是Bins (Bar)  数目是30, but S channel can till to 32 Bins.  我们想象MAT 里面看到直方图,那里是gray -level 的,也就是一个channel 的,把这个想个一个unit,1 channel on unit,他的水平轴就是hitsSize 的每个值。

      


  • ranges – Array of the dims arrays of the histogram bin boundaries in each dimension. When the histogram is uniform (uniform =true), then for each dimensioni it is enough to specify the lower (inclusive) boundaryL_0 of the 0-th histogram bin and the upper (exclusive) boundaryU_{\texttt{histSize}[i]-1} for the last histogram binhistSize[i]-1 . That is, in case of a uniform histogram each ofranges[i] is an array of 2 elements. When the histogram is not uniform (uniform=false ), then each ofranges[i] containshistSize[i]+1 elements:L_0, U_0=L_1, U_1=L_2, ..., U_{\texttt{histSize[i]}-2}=L_{\texttt{histSize[i]}-1}, U_{\texttt{histSize[i]}-1} . The array elements, that are not between L_0 andU_{\texttt{histSize[i]}-1} , are not counted in the histogram.

      ranges[0] to rangs[dims-1] 也就是每一个channel 都有自己的范围,比如H is from 0 -179 ,S from 0-255 . 因此

     ranges[i] 的每一个元素也是一个数组,一个二维数组,指明每一个channel 的范围。



  • uniform – Flag indicating whether the histogram is uniform or not (see above).
  • accumulate – Accumulation flag. If it is set, the histogram is not cleared in the beginning when it is allocated. This feature enables you to compute a single histogram from several sets of arrays, or to update the histogram in time.

#include <cv.h>
#include <highgui.h>

using namespace cv;

int main( int argc, char** argv )
{
    Mat src, hsv;
    if( argc != 2 || !(src=imread(argv[1], 1)).data )
        return -1;

    cvtColor(src, hsv, CV_BGR2HSV);

    // Quantize the hue to 30 levels
    // and the saturation to 32 levels
    int hbins = 30, sbins = 32;
    int histSize[] = {hbins, sbins};
    // hue varies from 0 to 179, see cvtColor
    float hranges[] = { 0, 180 };//Note need to plus +1 for the max rangs
    // saturation varies from 0 (black-gray-white) to
    // 255 (pure spectrum color)
    float sranges[] = { 0, 256 };
    const float* ranges[] = { hranges, sranges };
    MatND hist;
    // we compute the histogram from the 0-th and 1-st channels
    int channels[] = {0, 1};

显然这里30 个 Bin 去表达180 个value的intensity. ie:Bin0 就是0-5 的H value.

 calcHist( &hsv, 1, channels, Mat(), // do not use mask
             hist, 2, histSize, ranges,
             true, // the histogram is uniform
             false );
    double maxVal=0;
    minMaxLoc(hist, 0, &maxVal, 0, 0);

    int scale = 10;
    Mat histImg = Mat::zeros(sbins*scale, hbins*10, CV_8UC3);

    for( int h = 0; h < hbins; h++ )
        for( int s = 0; s < sbins; s++ )
        {
            float binVal = hist.at<float>(h, s); //Note that the value stored as float in the histogram
            int intensity = cvRound(binVal*255/maxVal);
            rectangle( histImg, Point(h*scale, s*scale),
                        Point( (h+1)*scale - 1, (s+1)*scale - 1),
                        Scalar::all(intensity),
                        CV_FILLED );
        }

    namedWindow( "Source", 1 );
    imshow( "Source", src );

    namedWindow( "H-S Histogram", 1 );
    imshow( "H-S Histogram", histImg );
    waitKey();
}



 



=======My Test version

#include "openCV.h"
int main()

{
    const string imagePath="c://image//test.png";
    cv::Mat image(3,4,CV_8UC3,Scalar(50,100,150));
    const string sourceImageWindowName="Source Image";
//    namedWindow(sourceImageWindowName,CV_WINDOW_AUTOSIZE);
     imshow(sourceImageWindowName,image);
//    imwrite(imagePath,image);


    int channels[3]={0,1,2};
    int hSize[3]={256,256,256};
    float range[2]={0,255};
    const float * hRange[3]={range,range,range};

    MatND hist;
    calcHist(&image,1,channels,Mat(),hist,2,hSize,hRange,true,false);
    cout<<hist.rows<<" "<<hist.cols<<endl;
    
    for(int r=0;r<hist.rows;++r)
    {
       for(int c=0;c<hist.cols;++c)
       {
           float histValue=hist.at<float>(r,c);
           if(histValue>0)
           cout<<r<<" "<<c<<" "<< histValue<<endl;
       }
    
    }

//可以看到打印出来的结果:50 100 12,也就是 channel[0]=50 &channel[1]=100 的pixel 有12 个,

2 demension Mat 有256*256 ,calcHist will calculate each possible 组合,然后算出这些组合有多少个像素。 注意我用的是像素而不是channel,calcHist 不是bye channel去计算,比如channel[0] 0-255 每一个value有多少个,从而得到

256*2 的Mat 结果,,之前的这个想法是错的。 同理3 dimension will calculate 256*256*256 个组合所有的像素个数

所以会得出很多0 , as follow show:

    MatND hist3D;
    calcHist(&image,1,channels,Mat(),hist3D,3,hSize,hRange,true,false);

    cout<<"total:"<<hist3D.total()<<endl;
    float histValu=hist3D.at<float>(50,100,150);// get 50,100,150出的value,因为我理解这个计算,So I do it jus at here.
    cout<<"hValue"<<histValu<<endl; //print 12

//because will generate lot's of zero, that will wast memroy. we use sparseMat

    SparseMat sparsemat(3,hSize,CV_32FC1);
    calcHist(&image,1,channels,Mat(),sparsemat,3,hSize,hRange);
    cout<<"sparseMat size"<<sparsemat.size()[0]<<" "<<sparsemat.size()[1]<<" "<< sparsemat.size()[2]<<endl;
    //ptrdiff_t pDiff;.

    int dims=sparsemat.dims();
    SparseMatConstIterator
        it=sparsemat.begin(),
        itEnd=sparsemat.end();
   for(;it!=itEnd;++it)
   {
      const SparseMat::Node * node=it.node();
       cout<<"(";
       for(size_t i=0;i<dims;i++)
           cout<<node->idx[i]<<",";
       cout<<")";
       cout<<it.value<float>()<<endl;
  ///   (50,100,150,)12,虽然SparseMat 不存0,但他的order 不边,so 12 will store at 50 100 150
      // cout<<(*it)<<endl;
   }
        


    waitKey(0);
return 0;
}

==========print result=======

256 256
50 100 12
total:16777216
hValue12
sparseMat size256 256 256
(50,100,150,)12



=========SparseMat study==============

The class SparseMat represents multi-dimensional sparse numerical arrays. Such a sparse array can store elements of any type thatMat can store. Sparse means that only non-zero elements are stored (though, as a result of operations on a sparse matrix, some of its stored elements can actually become 0. It is up to you to detect such elements and delete them using SparseMat::erase ). The non-zero elements are stored in a hash table that grows when it is filled so that the search time is O(1) in average (regardless of whether element is there or not). Elements can be accessed using the following methods:

  • Query operations (SparseMat::ptr and the higher-levelSparseMat::ref,SparseMat::value andSparseMat::find), for example:

    const int dims = 5;
    int size[] = {10, 10, 10, 10, 10};
    SparseMat sparse_mat(dims, size, CV_32F);//5 维,每一维是10个元素,so will have 10^5 element
    for(int i = 0; i < 1000; i++)
    {
        int idx[dims];
        for(int k = 0; k < dims; k++)
            idx[k] = rand()
  •         //idx 其实就相当于这个5维空间的坐标拉,,,
  •  sparse_mat.ref<float>(idx) += 1.f; //assign value to this point
    }
    
  • Sparse matrix iterators. They are similar toMatIterator but different fromNAryMatIterator. That is, the iteration loop is familiar to STL users:

    // prints elements of a sparse floating-point matrix
    // and the sum of elements.
    SparseMatConstIterator_<float>
        it = sparse_mat.begin<float>(),
        it_end = sparse_mat.end<float>();
    double s = 0;
    int dims = sparse_mat.dims();
    for(; it != it_end; ++it)
    {
        // print element indices and the element value
        const SparseMat::Node* n = it.node();
        printf("(");
        for(int i = 0; i < dims; i++)
            printf("%d%s", n->idx[i], i < dims-1 ? ", " : ")");
        printf(": %g\n", it.value<float>());  //打印非零元素的坐标and its value
        s += *it;
    }
    printf("Element sum is %g\n", s);
    

    If you run this loop, you will notice that elements are not enumerated in a logical order (lexicographical, and so on). They come in the same order as they are stored in the hash table (semi-randomly). You may collect pointers to the nodes and sort them to get the proper ordering. Note, however, that pointers to the nodes may become invalid when you add more elements to the matrix. This may happen due to possible buffer reallocation.

  • Combination of the above 2 methods when you need to process 2 or more sparse matrices simultaneously. For example, this is how you can compute unnormalized cross-correlation of the 2 floating-point sparse matrices:

    double cross_corr(const SparseMat& a, const SparseMat& b)
    {
        const SparseMat *_a = &a, *_b = &b;
        // if b contains less elements than a,
        // it is faster to iterate through b
        if(_a->nzcount() > _b->nzcount())
            std::swap(_a, _b);
        SparseMatConstIterator_<float> it = _a->begin<float>(),
                                       it_end = _a->end<float>();
        double ccorr = 0;
        for(; it != it_end; ++it)
        {
            // take the next element from the first matrix
            float avalue = *it;
            const Node* anode = it.node();
            // and try to find an element with the same index in the second matrix.
            // since the hash value depends only on the element index,
            // reuse the hash value stored in the node
            float bvalue = _b->value<float>(anode->idx,&anode->hashval);
            ccorr += avalue*bvalue;
        }
        return ccorr;
    }
    

Applying look-up tables to modify   image appearance


 look-up table is a  simple mapping function, to modify the pixel values of an image.

其实就是一个Array, 定义了all pixel value will map to what new value. ie. for 2 D image. we deine a

Mat (256*256),each element represent a pixel, ie (50,100)=(new value),means color channel 0,1 at value(50,100), will change to new value. 显然each pixel's position 没考虑,just focus on the pixel value itself.


A look-up table is a simple one-to-one (or many-to-one) function that defines how pixel values
are transformed into new values.




// Create an image inversion table

int dim(256);
cv::Mat lut(1, // 1 dimension
&dim, // 256 entries
CV_8U); // uchar
for (int i=0; i<256; i++) {
lut.at<uchar>(i)= 255-i;
}


  Mat result;
       cv::LUT(image,lut,result);
  cout<<result<<endl;


(50,100,150,)12 , all the 50 100 150 change to[ 205 155 105 ] ,虽然只是定义了一个1 dimesional 的mapping table,

but all the channel on the image will apply on the LUT


[205, 155, 105, 205, 155, 105, 205, 155, 105, 205, 155, 105;
  205, 155, 105, 205, 155, 105, 205, 155, 105, 205, 155, 105;
  205, 155, 105, 205, 155, 105, 205, 155, 105, 205, 155, 105]



You can also define a look-up table that tries to improve an image's contrast.

For example, if  you observe the original histogram of the previous image shown in the first recipe, it is easy
to notice that the full range of possible intensity values is not used (in particular, for this
image, the brighter intensity values are not used in the image).
One can therefore stretch the
histogram in order to produce an image with an expanded contrast.


The procedure is designed  to detect the lowest (imin) and the highest (imax) intensity value with non-zero count in the   image histogram.(找到最小于最大像素 (数量)) The intensity values can then be remapped such that the imin value is   repositioned at intensity 0, and the imax is assigned value 255. The in-between intensities i
are simply linearly remapped as follows:
255.0*(i-imin)/(imax-imin)+0.5);


cv::Mat stretch(const cv::Mat &image, int minValue=0) {
// Compute histogram first
cv::MatND hist= getHistogram(image);
// find left extremity of the histogram
int imin= 0;
for( ; imin < histSize[0]; imin++ ) {
std::cout<<hist.at<float>(imin)<<std::endl;
if (hist.at<float>(imin) > minValue)
break;

}
// find right extremity of the histogram
int imax= histSize[0]-1;
for( ; imax >= 0; imax-- ) {
if (hist.at<float>(imax) > minValue)
break;
}
// Create lookup table
int dim(256);
cv::Mat lookup(1, // 1 dimension
&dim, // 256 entries
CV_8U); // uchar
// Build lookup table
for (int i=0; i<256; i++) {
// stretch between imin and imax
if (i < imin) lookup.at<uchar>(i)= 0;
else if (i > imax) lookup.at<uchar>(i)= 255;
// linear mapping
else lookup.at<uchar>(i)= static_cast<uchar>(
255.0*(i-imin)/(imax-imin)+0.5);
}
// Apply lookup table
cv::Mat result;
result= applyLookUp(image,lookup);
return result;
}


他的算法思想其实是这样, 在histogram image 上给定一个intensity value ie=100, then y=100  f(x1)=100. f(x2)=100

那么在[x1  x2] region curve, 进行线性拉伸到[0-255]



// ignore starting and ending bins with less than 100 pixels
cv::Mat streteched= h.stretch(image,100);// 



Equalizing the image histogram

In  fact, one can think that a good-quality image should make equal use of all available pixel
intensities. This is the idea behind the concept of histogram equalization, that is making the
image histogram as flat as possible
.


equalizeHist

Equalizes the histogram of a grayscale image.

C++: void equalizeHist(InputArray src, OutputArray dst)
C: void cvEqualizeHist(const CvArr* src, CvArr* dst)
    Parameters:    

        src – Source 8-bit single channel image.
        dst – Destination image of the same size and type as src .

The function equalizes the histogram of the input image using the following algorithm:

    Calculate the histogram H for src .

    Normalize the histogram so that the sum of histogram bins is 255.

    Compute the integral of the histogram:

    H'_i = \sum _{0 \le j < i} H(j)

    Transform the image using H' as a look-up table: \texttt{dst}(x,y) = H'(\texttt{src}(x,y))

The algorithm normalizes the brightness and increases the contrast of the image.













GRAY2BGR 时候,所有的BGR channel will just apply same gray level value

Source image [50 100 150 ]

===========convert to Gray level
[109, 109, 109, 109;
  109, 109, 109, 109;
  109, 109, 109, 109]
=========== change back to RGB
[109, 109, 109, 109, 109, 109, 109, 109, 109, 109, 109, 109; 
  109, 109, 109, 109, 109, 109, 109, 109, 109, 109, 109, 109;
  109, 109, 109, 109, 109, 109, 109, 109, 109, 109, 109, 109]











       
 














内容概要:本文深入解析了扣子COZE AI编程及其详细应用代码案例,旨在帮助读者理解新一代低门槛智能体开发范式。文章从五个维度展开:关键概念、核心技巧、典型应用场景、详细代码案例分析以及未来发展趋势。首先介绍了扣子COZE的核心概念,如Bot、Workflow、Plugin、Memory和Knowledge。接着分享了意图识别、函数调用链、动态Prompt、渐进式发布及监控可观测等核心技巧。然后列举了企业内部智能客服、电商导购助手、教育领域AI助教和金融行业合规质检等应用场景。最后,通过构建“会议纪要智能助手”的详细代码案例,展示了从需求描述、技术方案、Workflow节点拆解到调试与上线的全过程,并展望了多智能体协作、本地私有部署、Agent2Agent协议、边缘计算插件和实时RAG等未来发展方向。; 适合人群:对AI编程感兴趣的开发者,尤其是希望快速落地AI产品的技术人员。; 使用场景及目标:①学习如何使用扣子COZE构建生产级智能体;②掌握智能体实例、自动化流程、扩展能力和知识库的使用方法;③通过实际案例理解如何实现会议纪要智能助手的功能,包括触发器设置、下载节点、LLM节点Prompt设计、Code节点处理和邮件节点配置。; 阅读建议:本文不仅提供了理论知识,还包含了详细的代码案例,建议读者结合实际业务需求进行实践,逐步掌握扣子COZE的各项功能,并关注其未来的发展趋势。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值