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 imageint 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 imageint 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)