scratch lenet(2): C语言实现图像直方图的计算
1. 目的
用 C 语言实现 uint8 类型图像(单通道)的直方图计算。不涉及直方图均衡化。
2. 什么是图像直方图
2.1 统计得到图像直方图
通常是对于单通道的灰度图而言的。像素范围是 [0, 255], 统计每个像素出现的次数, 存放到一个 256 元素的一维数组 hist
中。换言之, hist
是一个统计结果。
void calculate_histogram(uchar* image, int width, int height, int hist[256])
{
memset(hist, 0, 256 * sizeof(int));
for (int i = 0; i < height; i++)
{
for (int j = 0; j < width; j++)
{
int idx = i * width + j;
uchar v = image[idx];
hist[v]++;
}
}
printf("[DEBUG] print histogram:\n");
for (int i = 0; i < 256; i++)
{
if (hist[i] > 0)
{
printf("gray value=%d, occurance cnt=%d\n", i, hist[i]);
}
}
}
2.2 可视化:直方图转为图像表示
所谓可视化直方图, 是说把原本是1维的、256 元素的数组 hist[256]
, 转为2维图像。 图像宽度是256, 高度是 max(hist[i])
.
在计算机视觉中, 白色对应到255,黑色对应到0.
在计算机视觉的C/C++实现中,图像坐标原点是左上角, 而数学中的绘制图像曲线,左下角才是原点。
于是, 需要在获取图像坐标点 P(i, j)
时, 执行坐标变换,得到以左下角为原点时的坐标 P'(inv_i, j)
。
P'
的取值为 0 或 255: 如果比 hist[j]
要大(坐标点更高),则为255; 否则为0.
typedef struct GrayImage
{
int width;
int height;
uchar* data;
} GrayImage;
GrayImage create_gray_image_from_histogram(int hist[256])
{
// draw histogram as image
int max_hist = 0;
for (int i = 0; i < 256; i++)
{
if (hist[i] > max_hist)
{
max_hist = hist[i];
}
}
int hist_height = max_hist;
int hist_width = 256;
uchar* image = (uchar*)malloc(hist_height * hist_width);
for (int i = 0; i < hist_height; i++)
{
int inv_i = hist_height - 1 - i;
for (int j = 0; j < hist_width; j++)
{
int idx = i * hist_width + j;
if (inv_i > hist[j])
{
image[idx] = 255;
}
else
{
image[idx] = 0;
}
}
}
GrayImage hist_image;
hist_image.width = hist_width;
hist_image.height = hist_height;
hist_image.data = image;
return hist_image;
}
2.3 可视化:保存结果图
使用 .pgm 格式。使用 scratch lenet(1): 读写 pgm 图像文件 中实现的 .pgm 图像读写函数。
void write_pgm_image(uchar* image, int width, int height, const char* filename)
{
FILE* fout = fopen(filename, "wb");
fprintf(fout, "P5\n%d %d\n255\n", width, height);
fwrite(image, width * height, 1, fout);
fclose(fout);
}
3. 完整代码和结果
#include <stdio.h>
#include <stdlib.h>
typedef unsigned char uchar;
void write_pgm_image(uchar* image, int width, int height, const char* filename)
{
FILE* fout = fopen(filename, "wb");
fprintf(fout, "P5\n%d %d\n255\n", width, height);
fwrite(image, width * height, 1, fout);
fclose(fout);
}
void* memset(void* s, int c, size_t n)
{
char x = c & 0xff;
char* p = (char*)s;
for (int i = 0; i < n; i++)
{
p[i] = n;
}
return s;
}
void calculate_histogram(uchar* image, int width, int height, int hist[256])
{
memset(hist, 0, 256 * sizeof(int));
for (int i = 0; i < height; i++)
{
for (int j = 0; j < width; j++)
{
int idx = i * width + j;
uchar v = image[idx];
hist[v]++;
}
}
printf("[DEBUG] print histogram:\n");
for (int i = 0; i < 256; i++)
{
if (hist[i] > 0)
{
printf("gray value=%d, occurance cnt=%d\n", i, hist[i]);
}
}
}
typedef struct GrayImage
{
int width;
int height;
uchar* data;
} GrayImage;
GrayImage create_gray_image_from_histogram(int hist[256])
{
// draw histogram as image
int max_hist = 0;
for (int i = 0; i < 256; i++)
{
if (hist[i] > max_hist)
{
max_hist = hist[i];
}
}
int hist_height = max_hist;
int hist_width = 256;
uchar* image = (uchar*)malloc(hist_height * hist_width);
for (int i = 0; i < hist_height; i++)
{
int inv_i = hist_height - 1 - i;
for (int j = 0; j < hist_width; j++)
{
int idx = i * hist_width + j;
if (inv_i > hist[j])
{
image[idx] = 255;
}
else
{
image[idx] = 0;
}
}
}
GrayImage hist_image;
hist_image.width = hist_width;
hist_image.height = hist_height;
hist_image.data = image;
return hist_image;
}
int main()
{
uchar image[8 * 8] = {
52, 55, 61, 66, 70, 61, 64, 73,
63, 59, 55, 90, 109, 85, 69, 72,
62, 59, 68, 113, 144, 104, 66, 73,
63, 58, 71, 122, 154, 106, 70, 69,
67, 61, 68, 104, 126, 88, 68, 70,
79, 65, 60, 70, 77, 68, 58, 75,
85, 71, 64, 59, 55, 61, 65, 83,
87, 79, 69, 68, 65, 76, 78, 94
};
int width = 8;
int height = 8;
int hist[256] = { 0 };
calculate_histogram(image, width, height, hist);
GrayImage hist_image = create_gray_image_from_histogram(hist);
write_pgm_image(hist_image.data, hist_image.width, hist_image.height, "histogram.pgm");
free(hist_image.data);
}
运行结果
4. References
- 直方图均衡化(HE)