深入探究图像增强(C语言实现)

embedded/2024/10/17 19:29:08/

我们将从基础出发使用C语言进行图像处理与分析,重点讨论图像增强和平滑技术。图像增强技术旨在通过增加对比度、亮度和整体清晰度来改善图像的视觉质量。另一方面,图像平滑方法则用于减少噪声并减少图像中的突变,使图像更加均匀和视觉上吸引人。

使用C语言作为实现方式是因为只有当你手敲一遍这些代码,你才会对这些方法有更加深刻的理解


我们在前两次的笔记中已经实现了图像处理的基本操作

分别是

  • 图像每一个像素灰度值的读入
  • 图像灰度值直方图的显示

今天的目标实际上也很小,我们的目标就是实现图像增强

书上一共讲了两种办法,分别是

  1. 灰度线性拉伸算法
  2. 直方图均衡化

我们一个一个来尝试

灰度线性拉伸算法

我们先来看代码

void LinearStretchDemo(BYTE* pGryImg, int width, int height, double k, double b)
{BYTE* pCur, * pEnd;for(pCur = pGryImg,pEnd = pGryImg + width + height;pCur < pEnd;)//这个地方书上是这么写的,我在下面还是写成了标准写法{*(pCur++) = LUT[ * pCur];}return;
}

总体上来看还是比较简单,我们来试着写成C语言版本

void LinearStretchDemo(uint8_t* pGryImg, int width, int height, double k, double b) 
{uint8_t* pCur, * pEnd;for (pCur = pGryImg, pEnd = pGryImg + width * height; pCur < pEnd; pCur++) {*pCur = LUT[*pCur];}
}

这个代码前面讲过好多了,这里就不再多讲了,有兴趣可以去看看C语言图像的读入那一篇笔记

但是这里面LUT是什么意思呢?

LUT 是 Look-Up Table 的缩写,中文意思是查找表或者映射表。在图像处理中,LUT 是一种非常常见的技机,用于对图像进行颜色或灰度值的映射和调整。

具体来说,LUT 是一个数组或者表格,其中存储了输入值到输出值的对应关系。在图像处理中,通常用 LUT 来实现颜色校正、对比度调整、灰度拉伸等操作。例如,在灰度拉伸中,LUT 存储了原始灰度值到拉伸后的灰度值之间的映射关系。

使用 LUT 的好处在于,它可以提高图像处理的效率,并且允许我们通过简单的表格查询来实现复杂的颜色或灰度值调整。同时,LUT 也可以在不同的图像处理算法中重复使用,提高了算法的复用性和可维护性。

总而言之,LUT 是图像处理中非常有用的工具,它通过预先计算和存储输入值到输出值的映射关系,帮助我们快速、高效地对图像进行颜色和灰度值的调整。

这里如果每个灰度值都有一个映射值,那么就不用进行重复大量的计算了,只需要计算255次即可

这么说不是很好理解,我们直接来看书上下一个算法

这里我直接写成C语言版本

void LinearStretchDemo(uint8_t* pGryImg, int width, int height, double k, double b)
{uint8_t* pCur, * pEnd;int LUT[256];    //因为只有[0,255]共256个灰度值//step1. 生成查找表for (int g = 0; g < 256; g++){LUT[g] = max(0, min(255, k * g + b));}//step2. 进行变换for (pCur = pGryImg, pEnd = pGryImg + width * height; pCur < pEnd; pCur++){*pCur = LUT[*pCur];}//step3. 结束return;
}

这段代码是一个实现灰度图像线性拉伸处理的函数 LinearStretchDemo。让我来逐步解释这段代码的具体实现:

  1. uint8_t* pCur, * pEnd;:定义了两个指针变量,pCur 用于指向当前处理的像素值,pEnd 指向图像数据的末尾。
  2. int LUT[256];:定义了一个大小为 256 的整型数组,用于存储灰度值的映射关系。
  3. 生成查找表部分:
    • for (int g = 0; g < 256; g++):遍历所有可能的灰度值(0 到 255)。
    • LUT[g] = max(0, min(255, k * g + b));:对于每个灰度值,根据线性拉伸的公式 k * g + b 计算新的灰度值,并确保其范围在 0 到 255 之间,以防止越界。
  4. 进行变换部分:
    • for (pCur = pGryImg, pEnd = pGryImg + width * height; pCur < pEnd; pCur++):遍历图像数据中的每个像素。
    • *pCur = LUT[*pCur];:使用查找表 LUT 将当前像素的灰度值映射为线性拉伸后的新灰度值。
  5. 返回处理结果并结束函数。
我们来试一下

首先来讲一下传参

image-20240419193010726

  1. pGryImg:这是一个指向灰度图像数据的指针。灰度图像是一个二维数组,存储了图像中每个像素的灰度值。通过这个指针,函数能够访问图像的像素数据。
  2. width:这是图像的宽度,表示图像中每行像素的数量。它告诉函数每行有多少像素数据。
  3. height:这是图像的高度,表示图像中有多少行像素。它告诉函数图像有多少行数据。
  4. k:这是一个 double 类型的参数,代表线性拉伸的斜率。它控制着拉伸的速率或程度。当 ( k ) 大于 1 时,图像的对比度增加;当 ( k ) 小于 1 时,对比度降低。
  5. b:这也是一个 double 类型的参数,代表线性拉伸的偏移。它控制着拉伸后灰度值的起始位置。当 ( b ) 大于 0 时,图像的整体亮度增加;当 ( b ) 小于 0 时,整体亮度减小。

看效果

image-20240419194326354

简单的处理以后效果其实还是不错的

我们用这两个函数看一下数值

这两个函数的讲解在这篇文章

图像处理与图像分析—图像统计特性的计算(纯C语言实现灰度值显示)-CSDN博客

//统计图像灰度值
//pImg:灰度图像数据的指针。
//width:图像的宽度。
//height:图像的高度。
//* histogram:数组首元素地址,需要一个能储存256个变量的整型数组
void GetHistogram(uint8_t* pImg, int width, int height, int* histogram)
{uint8_t* pCur;uint8_t* pEnd = pImg + width * height;// 初始化直方图数组memset(histogram, 0, sizeof(int) * 256);// 直方图统计for (pCur = pImg; pCur < pEnd;){histogram[*pCur]++;pCur++;}// 函数结束return;
}//亮度和对比度
//储存histogram灰度直方图的指针
//接收亮度的变量地址
//接收对比度的变量地址
void GetBrightContrast(int* histogram, double* bright, double* contrast)
{int g;double sum, num; //书上说图像很亮时,int有可能会溢出,所以我这里直接用doubledouble fsum;//step.1 求亮度for (sum = num = 0, g = 0; g < 256; g++){sum += histogram[g] * g;num += histogram[g];}*bright = sum * 1.0 / num;//step.2 求对比度for (fsum = 0.0, g = 0; g < 256; g++){fsum += histogram[g] * (g - *bright) * (g - *bright);}*contrast = sqrt(fsum / (num - 1)); //即Std Dev//step.3 结束return;
}

image-20240419202007702

直方图的均衡化与规定化

image-20240419202356451

其实很好理解,就是把集中在某一区域的灰度值均匀的平铺在整体区域

我们还是先来看书上的代码

void RmwHistogramEqualizeDemo(BYTE *pGryImg, int width, int height)
{// 定义变量BYTE *pCur, *pEnd = pGryImg + width * height; // 指针变量,指向当前像素和图像末尾int histogram[256], A[256], LUT[256], g; // 直方图数组、累积直方图数组、查找表和灰度级// step.1-------------求直方图--------------------------//memset(histogram, 0, sizeof(int) * 256); // 初始化直方图数组为0for (pCur = pGryImg; pCur < pEnd;)histogram[*(pCur++)]++; // 统计每个灰度级出现的频率// step.2-------------求A[g],N-------------------------//for (g = 1, A[0] = histogram[0]; g < 256; g++){A[g] = A[g - 1] + histogram[g]; // 计算累积直方图数组}// step.3-------------求LUT[g]-------------------------//for (g = 0; g < 256; g++)LUT[g] = 255 * A[g] / (width * height); // 计算直方图均衡化后的灰度级// step.4-------------查表------------------------------//for (pCur = pGryImg; pCur < pEnd;)*(pCur++) = LUT[*pCur]; // 使用查找表对每个像素进行映射// step.5-------------结束------------------------------//return;
}

是不是还能继续优化

void RmwHistogramEqualize(BYTE *pGryImg, int width, int height)
{BYTE *pCur, *pEnd = pGryImg + width * height; // 指针变量,指向当前像素和图像末尾int histogram[256], LUT[256], A, g; // 直方图数组、查找表数组、累积直方图、灰度级// step.1-------------求直方图--------------------------//memset(histogram, 0, sizeof(int) * 256); // 初始化直方图数组为0for (pCur = pGryImg; pCur < pEnd;) histogram[*(pCur++)]++; // 统计每个灰度级出现的频率// step.2-------------求LUT[g]-------------------------//A = histogram[0]; // 初始化累积直方图的值为第一个灰度级的频率LUT[0] = 255 * A / (width * height); // 计算第一个灰度级对应的均衡化后的灰度值for (g = 1; g < 256; g++){A += histogram[g]; // 更新累积直方图的值LUT[g] = 255 * A / (width * height); // 计算当前灰度级对应的均衡化后的灰度值}// step.3-------------查表------------------------------//for (pCur = pGryImg; pCur < pEnd;) *(pCur++) = LUT[*pCur]; // 使用查找表对每个像素进行灰度映射// step.4-------------结束------------------------------//return;
}

接下来改为C语言版

void RmwHistogramEqualize(uint8_t *pGryImg, int width, int height) 
{uint8_t *pCur, *pEnd = pGryImg + width * height; // 指针变量,指向当前像素和图像末尾int histogram[256], LUT[256], A, g; // 直方图数组、查找表数组、累积直方图、灰度级// step.1-------------求直方图--------------------------//memset(histogram, 0, sizeof(int) * 256); // 初始化直方图数组为0for (pCur = pGryImg; pCur < pEnd;)  histogram[*(pCur++)]++; // 统计每个灰度级出现的频率// step.2-------------求LUT[g]-------------------------//A = histogram[0]; // 初始化累积直方图的值为第一个灰度级的频率LUT[0] = 255 * A / (width * height); // 计算第一个灰度级对应的均衡化后的灰度值for (g = 1; g < 256; g++)  {A += histogram[g]; // 更新累积直方图的值LUT[g] = 255 * A / (width * height); // 计算当前灰度级对应的均衡化后的灰度值}// step.3-------------查表------------------------------//for (pCur = pGryImg; pCur < pEnd;)  *(pCur++) = LUT[*pCur]; // 使用查找表对每个像素进行灰度映射// step.4-------------结束------------------------------//return;
}

我们来看看实现效果怎么样

image-20240419215512601

image-20240419222847826

可以看到效果还是很不错的

对数变换

人眼对于亮度变化的反应是随着光的增加而减弱,实验证明人眼的这种性质更近似于对数函数

对数变换是一种常见的图像处理技术,通常用于增强图像的对比度或调整图像的亮度。它的原理是通过对图像的像素值取对数来调整像素值的分布,从而改变图像的外观。

在对数变换中,常用的是自然对数函数(以e为底的对数函数),其公式为:

image-20240419221718879

其中:

  • ( s ) 是输出图像的像素值;
  • ( r ) 是输入图像的像素值;
  • ( c ) 是一个常数,用于调节对比度;
  • ( \log ) 是自然对数函数。

对数变换的特点包括:

  1. 对数压缩特性:对于输入像素值较小的区域,对数变换会对其进行较大程度的拉伸,从而增强了图像的对比度。这对于那些像素值分布在较低灰度级区域的图像非常有用,可以使得细节更加清晰可见。
  2. 对数拉伸特性:对于输入像素值较大的区域,对数变换会对其进行较小程度的拉伸,这有助于将高灰度级的区域拉伸到更广泛的灰度范围内,从而增强了图像的亮度表现。

对数变换的应用包括但不限于:

  • 图像增强:通过调整对数变换中的参数,可以增强图像的对比度,使得图像细节更加清晰。
  • 图像压缩:对数变换也可以用于压缩图像的动态范围,将大范围的灰度级映射到一个较小的范围内,从而方便存储和传输。

看代码

void RmwLogTransform(BYTE *pGryImg, int width, int height)
{BYTE *pCur, *pEnd = pGryImg + width * height; // 指向灰度图像数据的当前指针和结束指针int histogram[256], LUT[256], gmax, g; // 声明直方图数组、查找表数组、最大灰度值、当前灰度值double c; // 声明常数c// step.1-------------求直方图--------------------------//memset(histogram, 0, sizeof(int) * 256); // 初始化直方图数组为0for (pCur = pGryImg; pCur < pEnd;) histogram[*(pCur++)]++; // 遍历图像数据,统计每个灰度级的像素数量// step.2-------------最大值---------------------------//for (gmax = 255; gmax >= 0; gmax++) if (histogram[gmax]) break; // 从最大灰度级开始向低灰度级搜索,找到第一个非零灰度级,即最大灰度值// step.3-------------求LUT[g]-------------------------//c = 255.0 / log(1 + gmax); // 计算常数cfor (g = 0; g < 256; g++) {LUT[g] = (int)(c * log(1 + g)); // 根据对数变换公式计算查找表中每个灰度级的映射值}// step.4-------------查表------------------------------//for (pCur = pGryImg; pCur < pEnd;) *(pCur++) = LUT[*pCur]; // 使用查找表将图像数据进行对数变换// step.5-------------结束------------------------------//return; // 函数结束
}

改为C语言实现

void RmwLogTransform(uint8_t *pGryImg, int width, int height)
{uint8_t *pCur, *pEnd = pGryImg + width * height; // 指向灰度图像数据的当前指针和结束指针int histogram[256], LUT[256], gmax, g; // 声明直方图数组、查找表数组、最大灰度值、当前灰度值double c; // 声明常数c// step.1-------------求直方图--------------------------//memset(histogram, 0, sizeof(int) * 256); // 初始化直方图数组为0for (pCur = pGryImg; pCur < pEnd;) histogram[*(pCur++)]++; // 遍历图像数据,统计每个灰度级的像素数量// step.2-------------最大值---------------------------//for (gmax = 255; gmax >= 0; gmax++) if (histogram[gmax]) break; // 从最大灰度级开始向低灰度级搜索,找到第一个非零灰度级,即最大灰度值// step.3-------------求LUT[g]-------------------------//c = 255.0 / log(1 + gmax); // 计算常数cfor (g = 0; g < 256; g++) {LUT[g] = (int)(c * log(1 + g)); // 根据对数变换公式计算查找表中每个灰度级的映射值}// step.4-------------查表------------------------------//for (pCur = pGryImg; pCur < pEnd;) *(pCur++) = LUT[*pCur]; // 使用查找表将图像数据进行对数变换// step.5-------------结束------------------------------//return; // 函数结束
}

image-20240419222755214

增强方法更应该根据我们的需求来选择


源码

IDP.h

#pragma once#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <math.h>uint8_t* readGrayScaleBMP(const char* filename, int* width, int* height);//读取8位灰度图片
void saveGrayScaleBMP(const char* filename, const uint8_t* imageData, int width, int height);// 将8位灰度图像数据保存为BMP文件
uint8_t* readColorBMP(const char* filename, int* width, int* height);//读取24位彩色图像的BMP文件
void saveColorBMP(const char* filename, const uint8_t* imageData, int width, int height);//将24位彩色图像数据保存为BMP文件
void LinearStretchDemo(uint8_t* pGryImg, int width, int height, double k, double b);//灰度线性拉伸
void GetHistogram(uint8_t* pImg, int width, int height, int* histogram);//统计图像灰度值
void GetBrightContrast(int* histogram, double* bright, double* contrast);//亮度和对比度
void RmwHistogramEqualize(uint8_t* pGryImg, int width, int height);//直方图均衡化
void RmwLogTransform(uint8_t* pGryImg, int width, int height);//对数变换

IDP.C

#include "IDP.h"//读取8位灰度图片
//filename:字符数组的指针,用于指定要保存的图像文件的名称或路径。
//imageData:无符号 8 位整型数据的指针,代表要保存的图像数据。
//width:图像的宽度。
//height:图像的高度。
uint8_t* readGrayScaleBMP(const char* filename, int* width, int* height) 
{FILE* file = fopen(filename, "rb");if (!file) {fprintf(stderr, "Error opening file %s\n", filename);return NULL;}// 读取BMP文件头部信息uint8_t bmpHeader[54];fread(bmpHeader, 1, 54, file);// 从文件头部提取图像宽度和高度信息*width = *(int*)&bmpHeader[18];*height = *(int*)&bmpHeader[22];// 分配存储图像数据的内存uint8_t* imageData = (uint8_t*)malloc(*width * *height);if (!imageData) {fprintf(stderr, "内存分配失败\n");fclose(file);return NULL;}// 计算调色板的大小int paletteSize = *(int*)&bmpHeader[46];if (paletteSize == 0)paletteSize = 256;// 读取调色板数据uint8_t palette[1024];fread(palette, 1, paletteSize * 4, file);// 读取图像数据fseek(file, *(int*)&bmpHeader[10], SEEK_SET);fread(imageData, 1, *width * *height, file);fclose(file);return imageData;
}// 将8位灰度图像数据保存为BMP文件
//filename:字符数组的指针,用于指定要保存的图像文件的名称或路径。
//imageData:无符号 8 位整型数据的指针,代表要保存的图像数据。
//width:图像的宽度。
//height:图像的高度。
void saveGrayScaleBMP(const char* filename, const uint8_t* imageData, int width, int height) 
{FILE* file = fopen(filename, "wb");if (!file) {fprintf(stderr, "Error creating file %s\n", filename);return;}// BMP文件头部信息uint8_t bmpHeader[54] = {0x42, 0x4D,             // 文件类型标识 "BM"0x36, 0x00, 0x0C, 0x00, // 文件大小(以字节为单位,此处假设图像数据大小不超过4GB)0x00, 0x00,             // 保留字段0x00, 0x00,             // 保留字段0x36, 0x00, 0x00, 0x00, // 位图数据偏移(以字节为单位)0x28, 0x00, 0x00, 0x00, // 位图信息头大小(40字节)0x00, 0x00, 0x00, 0x00, // 图像宽度0x00, 0x00, 0x00, 0x00, // 图像高度0x01, 0x00,             // 目标设备的级别(此处为1,不压缩)0x08, 0x00,             // 每个像素的位数(8位)0x00, 0x00, 0x00, 0x00, // 压缩类型(此处为不压缩)0x00, 0x00, 0x00, 0x00, // 图像数据大小(以字节为单位,此处为0,表示不压缩)0x00, 0x00, 0x00, 0x00, // 水平分辨率(像素/米,此处为0,表示未知)0x00, 0x00, 0x00, 0x00, // 垂直分辨率(像素/米,此处为0,表示未知)0x00, 0x00, 0x00, 0x00, // 使用的颜色索引数(0表示使用所有调色板项)0x00, 0x00, 0x00, 0x00  // 重要的颜色索引数(0表示所有颜色都重要)};// 更新BMP文件头部信息中的宽度和高度*(int*)&bmpHeader[18] = width;*(int*)&bmpHeader[22] = height;// 写入BMP文件头部信息fwrite(bmpHeader, 1, 54, file);// 写入调色板数据for (int i = 0; i < 256; i++) {fputc(i, file);  // 蓝色分量fputc(i, file);  // 绿色分量fputc(i, file);  // 红色分量fputc(0, file);  // 保留字节}// 写入图像数据fwrite(imageData, 1, width * height, file);fclose(file);
}// 读取24位彩色图像的BMP文件
//filename:字符数组的指针,用于指定要读取的 BMP 格式图像文件的名称或路径。
//width:整型变量的指针,用于存储读取的图像的宽度。
//height:整型变量的指针,用于存储读取的图像的高度。
uint8_t* readColorBMP(const char* filename, int* width, int* height) 
{FILE* file = fopen(filename, "rb");if (!file) {fprintf(stderr, "Error opening file %s\n", filename);return NULL;}// 读取BMP文件头部信息uint8_t bmpHeader[54];fread(bmpHeader, 1, 54, file);// 从文件头部提取图像宽度和高度信息*width = *(int*)&bmpHeader[18];*height = *(int*)&bmpHeader[22];// 分配存储图像数据的内存uint8_t* imageData = (uint8_t*)malloc(*width * *height * 3);if (!imageData) {fprintf(stderr, "Memory allocation failed\n");fclose(file);return NULL;}// 读取图像数据fseek(file, *(int*)&bmpHeader[10], SEEK_SET);fread(imageData, 1, *width * *height * 3, file);fclose(file);return imageData;
}//将24位彩色图像数据保存为BMP文件
//filename:字符数组的指针,用于指定要保存的图像文件的名称或路径。
//imageData:无符号 8 位整型数据的指针,代表要保存的图像数据。
//width:图像的宽度。
//height:图像的高度。
void saveColorBMP(const char* filename, const uint8_t* imageData, int width, int height) 
{FILE* file = fopen(filename, "wb");if (!file) {fprintf(stderr, "Error creating file %s\n", filename);return;}// BMP文件头部信息uint8_t bmpHeader[54] = {0x42, 0x4D,             // 文件类型标识 "BM"0x00, 0x00, 0x00, 0x00, // 文件大小(占位,稍后计算)0x00, 0x00,             // 保留字段0x00, 0x00,             // 保留字段0x36, 0x00, 0x00, 0x00, // 位图数据偏移(以字节为单位)0x28, 0x00, 0x00, 0x00, // 位图信息头大小(40字节)0x00, 0x00, 0x00, 0x00, // 图像宽度0x00, 0x00, 0x00, 0x00, // 图像高度0x01, 0x00,             // 目标设备的级别(此处为1,不压缩)0x18, 0x00,             // 每个像素的位数(24位)0x00, 0x00, 0x00, 0x00, // 压缩类型(此处为不压缩)0x00, 0x00, 0x00, 0x00, // 图像数据大小(占位,稍后计算)0x00, 0x00, 0x00, 0x00, // 水平分辨率(像素/米,此处为0,表示未知)0x00, 0x00, 0x00, 0x00, // 垂直分辨率(像素/米,此处为0,表示未知)0x00, 0x00, 0x00, 0x00, // 使用的颜色索引数(0表示使用所有调色板项)0x00, 0x00, 0x00, 0x00  // 重要的颜色索引数(0表示所有颜色都重要)};// 更新BMP文件头部信息中的宽度和高度*(int*)&bmpHeader[18] = width;*(int*)&bmpHeader[22] = height;// 计算图像数据大小uint32_t imageDataSize = width * height * 3 + 54; // 加上文件头部大小bmpHeader[2] = (uint8_t)(imageDataSize & 0xFF);bmpHeader[3] = (uint8_t)((imageDataSize >> 8) & 0xFF);bmpHeader[4] = (uint8_t)((imageDataSize >> 16) & 0xFF);bmpHeader[5] = (uint8_t)((imageDataSize >> 24) & 0xFF);// 写入BMP文件头部信息fwrite(bmpHeader, 1, 54, file);// 写入图像数据fwrite(imageData, width * height * 3, 1, file);fclose(file);
}//灰度线性拉伸
//pGryImg:灰度图像数据的指针。
//width:图像的宽度。
//height:图像的高度。
//k:线性拉伸的斜率。它控制着拉伸的速率或程度。当(k) 大于 1 时,图像的对比度增加;当(k) 小于 1 时,对比度降低。
//b:线性拉伸的偏移。它控制着拉伸后灰度值的起始位置。当(b) 大于 0 时,图像的整体亮度增加;当(b) 小于 0 时,整体亮度减小。
void LinearStretchDemo(uint8_t* pGryImg, int width, int height, double k, double b)
{uint8_t* pCur, * pEnd;int LUT[256];    //因为只有[0,255]共256个灰度值//step1. 生成查找表for (int g = 0; g < 256; g++){LUT[g] = max(0, min(255, k * g + b));}//step2. 进行变换for (pCur = pGryImg, pEnd = pGryImg + width * height; pCur < pEnd; pCur++){*pCur = LUT[*pCur];}//step3. 结束return;
}//统计图像灰度值
//pImg:灰度图像数据的指针。
//width:图像的宽度。
//height:图像的高度。
//* histogram:数组首元素地址,需要一个能储存256个变量的整型数组
void GetHistogram(uint8_t* pImg, int width, int height, int* histogram)
{uint8_t* pCur;uint8_t* pEnd = pImg + width * height;// 初始化直方图数组memset(histogram, 0, sizeof(int) * 256);// 直方图统计for (pCur = pImg; pCur < pEnd;){histogram[*pCur]++;pCur++;}// 函数结束return;
}//亮度和对比度
//储存histogram灰度直方图的指针
//接收亮度的变量地址
//接收对比度的变量地址
void GetBrightContrast(int* histogram, double* bright, double* contrast)
{int g;double sum, num; //书上说图像很亮时,int有可能会溢出,所以我这里直接用doubledouble fsum;//step.1 求亮度for (sum = num = 0, g = 0; g < 256; g++){sum += histogram[g] * g;num += histogram[g];}*bright = sum * 1.0 / num;//step.2 求对比度for (fsum = 0.0, g = 0; g < 256; g++){fsum += histogram[g] * (g - *bright) * (g - *bright);}*contrast = sqrt(fsum / (num - 1)); //即Std Dev//step.3 结束return;
}//pGryImg:灰度图像数据的指针。
//width:图像的宽度。
//height:图像的高度。
void RmwHistogramEqualize(uint8_t* pGryImg, int width, int height)
{uint8_t* pCur, * pEnd = pGryImg + width * height; // 指针变量,指向当前像素和图像末尾int histogram[256], LUT[256], A, g; // 直方图数组、查找表数组、累积直方图、灰度级// step.1-------------求直方图--------------------------//memset(histogram, 0, sizeof(int) * 256); // 初始化直方图数组为0for (pCur = pGryImg; pCur < pEnd;)histogram[*(pCur++)]++; // 统计每个灰度级出现的频率// step.2-------------求LUT[g]-------------------------//A = histogram[0]; // 初始化累积直方图的值为第一个灰度级的频率LUT[0] = 255 * A / (width * height); // 计算第一个灰度级对应的均衡化后的灰度值for (g = 1; g < 256; g++) {A += histogram[g]; // 更新累积直方图的值LUT[g] = 255 * A / (width * height); // 计算当前灰度级对应的均衡化后的灰度值}// step.3-------------查表------------------------------//for (pCur = pGryImg; pCur < pEnd;)*(pCur++) = LUT[*pCur]; // 使用查找表对每个像素进行灰度映射// step.4-------------结束------------------------------//return;
}//对数变换
//pGryImg:灰度图像数据的指针。
//width:图像的宽度。
//height:图像的高度。
void RmwLogTransform(uint8_t* pGryImg, int width, int height)
{uint8_t* pCur, * pEnd = pGryImg + width * height; // 指向灰度图像数据的当前指针和结束指针int histogram[256], LUT[256], gmax, g; // 声明直方图数组、查找表数组、最大灰度值、当前灰度值double c; // 声明常数c// step.1-------------求直方图--------------------------//memset(histogram, 0, sizeof(int) * 256); // 初始化直方图数组为0for (pCur = pGryImg; pCur < pEnd;)histogram[*(pCur++)]++; // 遍历图像数据,统计每个灰度级的像素数量// step.2-------------最大值---------------------------//for (gmax = 255; gmax >= 0; gmax++)if (histogram[gmax]) break; // 从最大灰度级开始向低灰度级搜索,找到第一个非零灰度级,即最大灰度值// step.3-------------求LUT[g]-------------------------//c = 255.0 / log(1 + gmax); // 计算常数cfor (g = 0; g < 256; g++){LUT[g] = (int)(c * log(1 + g)); // 根据对数变换公式计算查找表中每个灰度级的映射值}// step.4-------------查表------------------------------//for (pCur = pGryImg; pCur < pEnd;)*(pCur++) = LUT[*pCur]; // 使用查找表将图像数据进行对数变换// step.5-------------结束------------------------------//return; // 函数结束
}

次回预告

图像的质量是什么,如何提高图像的质量,中值滤波,均值滤波,最小值滤波,最大值滤波,高斯滤波,二值图像滤波,数学形态滤波,条件滤波又都代表着什么?

关于噪声的处理方法,下一篇博文将会讲解图像平滑

感谢您的阅读~


http://www.ppmy.cn/embedded/4068.html

相关文章

开源大模型 Llama 3

开源大模型Llama 3是一个在多个领域都展现出卓越性能的大模型。下面将为您介绍Llama 3的特性和一些简单的使用案例。 一、Llama 3介绍 Llama 3在Meta自制的两个24K GPU集群上进行预训练&#xff0c;使用了超过15T的公开数据&#xff0c;其中5%为非英文数据&#xff0c;涵盖30多…

无风扇嵌入式车载电脑在矿山车辆行业应用

矿山车辆行业应用 背景介绍 现代的采矿业面临许多的挑战&#xff0c;其中最重要的就是安全性的问题&#xff0c;无论在矿井下或地面上的工作&#xff0c;都必须确保员工的安全保障。因此&#xff0c;先进的矿车必须整合专用的车载电脑&#xff0c;在极其恶劣的采矿环境中稳定运…

软件工程及开发模型

根据希赛相关视频课程汇总整理而成&#xff0c;个人笔记&#xff0c;仅供参考。 软件工程的基本要素包括方法、工具和&#xff08;过程&#xff09; 方法&#xff1a;完成软件开发的各项任务的技术方法&#xff1b; 工具&#xff1a;运用方法而提供的软件工程支撑环境&#xff…

Ubuntu 20.04 安装Nginx-1.25.4

操作系统&#xff1a;Ubuntu 20.04.5 LTS 软件版本&#xff1a;nginx-1.25.4 一、环境准备 安装GCC编译器 安装PCRE 安装OpenSSL 安装OpenSSL库 安装zlib库 # 安装GCC编译器&#xff08;build-essential 包会安装GCC编译器以及其他一些用于编译源代码的必须工具&#xff09;sud…

配置路由器实现互通

1.实验环境 实验用具包括两台路由器(或交换机)&#xff0c;一根双绞线缆&#xff0c;一台PC&#xff0c;一条Console 线缆。 2.需求描述 如图6.14 所示&#xff0c;将两台路由器的F0/0 接口相连&#xff0c;通过一台PC 连接设备的 Console 端口并配置P地址&#xff08;192.1…

安卓手机APP开发__媒体开发部分__直播流

安卓手机APP开发__媒体开发部分__直播流 目录 概述 检查和监控直播的播放 在直播流中的定位查找 直播播放的用户界面 配置直播播放的参数 播放速度调整 定制播放速度的调整算法 直播窗口背后的异常和ERROR_CODE_BEHIND_LIVE_WINDOW 概述 ExoPlayer没有任何特殊配置的…

ansible-playbook角色roles使用实例及遇到的问题排除记录

目录 1、角色roles的概念和作用 2、角色roles的目录层级结构 3、ansible-play使用角色roles部署服务实例 3.1 实例-使用ansible-playbook部署安装htttpd服务&#xff0c;且配置文件修改后会被触发启动。 3.2 遇到的问题记录 1、角色roles的概念和作用 角色roles是ansible自…

04 MySQL --DQL 专题--Union、exists

1. UNION、UNION ALL UNION 关键字的作用&#xff1f; 合并两个或多个 SELECT 语句的结果。发挥的作用与 or 非常相似 UNION关键字生效的前提&#xff1f; 每个 SELECT 语句必须拥有相同数量的列。每个 SELECT 语句中的列的顺序必须相同。列必须拥有相似的数据类型。 SELEC…