c语⾔统计数量⽤count_直⽅图均衡化,从⽤C语⾔数数开始温馨提⽰:本⽂只需C语⾔基础知识即可⾷⽤。
从⽤C语⾔数数开始
给你⼀些数,你能不能告诉我每个数出现了多少次?
我们可以这么做:先看看这堆数的范围有多⼤;然后从最⼩的数出发,⼀个个数去数过去,⼀直数到最⼤的数。
⽤C语⾔来描述这个过程,是怎样的呢?
具体代码如下:
int number[10] = { 2,5,0,1,2,3,5,7,2,2 }; //⽤来数数的10个数
int min, max; //这堆数⾥⾯最⼩的数和最⼤的数
int i, j;
//这⾥我们假设,第⼀个数就是最⼩(⼤)的
min = number[0];
max = number[0];
//到最⼩的数
for (i = 0; i < 10; i++) if (number[i] < min) min = number[i];
//到最⼤的数
for (i = 0; i < 10; i++) if (number[i] > max) max = number[i];
//从最⼩的数⼀直看过去
for (i = min; i <= max; i++)
{
int count = 0;//数⼀下这个数出现了多少次
for (j = 0; j < 10; j++) if (number[j] == i) count++;
printf("数字%d出现了%d次n", i, count);
}
咋⼀看,这么数数还很有条理呢!但是这样做还是有点⿇烦。
不妨我们换⼀个思路:开辟⼀个数组进⾏计数,每个元素表⽰某⼀个数的出现次数。这种操作在算法领域被称作“⽤空间换时间”。与此同时,⽤数组把这堆数存起来。把整个数组每个元素都检查⼀遍(整个数组扫描⼀遍),就可以得到每个数出现的次数了。
具体代码如下:
int number[10] = { 2,5,0,1,2,3,5,7,2,2 }; //⽤来数数的10个数
int count[10]; //⽤来计数
int i;
//初始化count数组
memset(count, 0, sizeof(count));
//只有⼀⾏的核⼼代码,惊不惊喜,意不意外
for (i = 0; i < 10; i++) count[number[i]]++;
这段代码是不是⽐上⼀段代码要短得多?
这段代码后⾯再加上⼀段按出现次数输出每个数的代码,就可以得到史上速度最快的排序算法——计数排序。该算法于1954年由 Harold H. Seward 提出。具体代码就不放出来了,留作读者习题(⼿动狗头)。
也给⼆维数组数数吧!
嗯呐,话题扯远了。不是要讨论直⽅图均衡化吗?直⽅图呢?客官莫急,直⽅图马上就好。现在我们把上⾯提及的“数组”推⼴⾄⼆维数组,但是同时将数的范围约束在0-255之间。这样得到的⼆维数组正是灰度图(⼀种⽊有颜⾊只有明暗区别的图像)。对灰度图的每⼀个元素(数字图像处理中称为像素)都检查⼀遍,最后得到的统计⽤的数组就是图像的灰度分布。把灰度分布画成柱状图,就是传说中的“直⽅图”。
以800*600的灰度图为例,灰度分布的具体⽣成代码如下:
unsigned char image[600][800]; //800*600的灰度图,假设已经加载到⼆维数组⾥
int count[256]; //⽤来计数
int i,j;
//初始化count数组
memset(count, 0, sizeof(count));
//仅仅两⾏的核⼼代码
for (i = 0; i < 800; i++)
for (j = 0; j < 600; j++) count[image[j][i]]++;
从灰度直⽅图可以看出图像的亮暗等⼀些图像特征。有时候图像会过亮,或者过暗。这个时候可以尝试将直⽅图变得均匀分布(实际上只能做到近似均匀),让图像增强对⽐度,使其⾊彩变得丰富起来。
例如下⾯这张图。这张图的亮度偏暗,对⽐度较低。从这张图的灰度直⽅图也可以看出来。(波峰集中在直⽅图的左半侧)
算法⽰例⽤的原图像
MATLAB⽣成的灰度直⽅图
叮咚~ 直⽅图均衡化来咯!
直⽅图均衡化(Histogram Equalization)是⼀种增强图像对⽐度(Image Contrast)的⽅法,其主要思想是
将⼀副图像的直⽅图分布变成近似均匀分布,从⽽增强图像的对⽐度。直⽅图均衡化虽然只是数字图像处理(Digital Image Processing)⾥⾯的基本⽅法,但是其作⽤很强⼤,是⼀种很经典的算法。
假设变换前的灰度为X,变换后的灰度为Y。想让图像的直⽅图变得(近似)均匀起来,那么我们就要到⼀个单调映射f:R→R。这种单调映射满⾜
。经过理论推导(这⾥省略具体过程),我们发现理想的映射恰好是累积分布函数(Cumulative Distribution Function,CDF)。设图像中灰度为
的个数为
,像素点总数为
;对于图像中的每个灰度值⽽⾔,相应的累积分布函数计算式为:
具体实现代码如下:
unsigned char img[600][800]; //800*600的灰度图
int count[256]; //⽤来计数
double Fx[256];//累积分布函数
int hist[256];//新的灰度
int i,j;
//初始化count数组
memset(count, 0, sizeof(count));
memset(Fx, 0, sizeof(Fx));
memset(hist, 0, sizeof(hist));
//先得到img的灰度分布
for (i = 0; i < 800; i++)
for (j = 0; j < 600; j++) count[img[j][i]]++;
//然后再根据累积分布函数的公式,求出0-255每个灰度的累积分布函数
for (i = 0; i < 256; i++)
{
for (j = 0; j <= i; j++) Fx[i] += count[j];
Fx[i] /= 800 * 600;
}
//根据累积分布函数,计算出原来的灰度和新的灰度之间的映射关系
for (i = 0; i < 256; i++) hist[i] = int(255 * Fx[i] + 0.5);
//将图像原来的灰度映射成新的灰度
for (i = 0; i < 800; i++)
for (j = 0; j < 600; j++) img[j][i] = hist[img[j][i]];
代码优化
上⾯的代码只是为了说明原理,还存在⼀部分的冗余代码。我们可以从这两⽅⾯⼊⼿,进⾏代码的优化:
1、累积分布函数的计算。观察累积分布函数的计算式,不难发现
。利⽤这⼀点,可以把累积分布函数的计算利⽤递推算法完成,从⽽减少计算量。
2、减少循环次数。我们可以在计算累积分布函数的同时,同时出原灰度和新的灰度之间的映射关系。即:将计算累积分布函数和出映射两部分的循环合并,减少循环计数变量的开销。
经过优化和改进后,最终代码如下:
unsigned char img[600][800]; //800*600的灰度图
int count[256]; //⽤来计数
int sum = 0; //从0开始的像素点数量的累加
int hist[256];//新的灰度
int i,j;
//初始化count数组
memset(count, 0, sizeof(count));
memset(Fx, 0, sizeof(Fx));
matlab直方图memset(hist, 0, sizeof(hist));
//先得到img的灰度分布
for (i = 0; i < 800; i++)
for (j = 0; j < 600; j++) count[img[j][i]]++;
//直接求出原来的灰度和新的灰度之间的映射关系
for (i = 0; i < 256; i++)
{
sum += count[i];
hist[i] = int(255 * sum / double(sizeof(img)) + 0.5);
}
//将图像原来的灰度映射成新的灰度
for (i = 0; i < 800; i++)
for (j = 0; j < 600; j++) img[j][i] = hist[img[j][i]];
最终效果⽰意
对这张图进⾏直⽅图均衡化处理后,可以看出图像⼀下⼦变得明亮起来了,变得明暗分明,富有层次感。从灰度直⽅图也可以看出来(灰度分布近似均匀)。
经过直⽅图均衡化之后的灰度图
处理后的灰度直⽅图
为什么处理之后,直⽅图看上去变得⽀离破碎了呢?原因是直⽅图均衡化后⼀些相近的灰度被四舍五⼊⾄某⼀个灰度值当中,从⽽使得直⽅图看上去离散起来了。
看到最后,你掌握了直⽅图均衡的算法了吗?可以的话,请尝试⽤OpenCV实现该算法(不允许使⽤equalizeHist函数)