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

我们将从基础出发使用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.mzph.cn/bicheng/640.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

2024新版淘宝客PHP网站源码

源码介绍 2024超好看的淘客PHP网站源码&#xff0c;可以做优惠券网站&#xff0c;上传服务器&#xff0c;访问首页进行安装 安装好了之后就可以使用了&#xff0c;将里面的信息配置成自己的就行 喜欢的朋友们拿去使用把 效果截图 源码下载 2024新版淘宝客网站源码

高精度算法(1)

前言 今天来讲一讲高精度算法&#xff0c;我们说一个数据类型&#xff0c;有它的对应范围比如int类型最多 可以包含到负2的31次方到2的31次方减一 其实大概就是20亿左右那么其他的类型也同样如此 那么&#xff0c;如何解决一个很大很大的数的运算呢&#xff1f; 我们今天介…

OSPF综合大实验

1、R4为ISP&#xff0c;其上只配置IP地址&#xff1b;R4与其他所直连设备间均使用公有IP&#xff1b; 2、R3-R5、R6、R7为MGRE环境&#xff0c;R3为中心站点&#xff1b; 3、整个OSPF环境IP基于172.16.0.0/16划分&#xff1b;除了R12有两个环回&#xff0c;其他路由器均有一个环…

局域网管理软件,适合在局域网内的管理软件有哪些?

信息技术的不断发展&#xff0c;局域网在企业、学校等机构中得到了广泛应用。 局域网不仅能够提高数据传输效率&#xff0c;还能实现资源共享和协同工作。 为了更好地管理和维护局域网&#xff0c;需要使用一些专业的局域网管理软件。 一、局域网的应用范围 局域网&#xff…

【算法一则】分隔链表

题目 给你一个链表的头节点 head 和一个特定值 x &#xff0c;请你对链表进行分隔&#xff0c;使得所有 小于 x 的节点都出现在 大于或等于 x 的节点之前。 你应当 保留 两个分区中每个节点的初始相对位置。 示例 1&#xff1a; 输入&#xff1a;head [1,4,3,2,5,2], x …

做一个图像分类任务(一)准备数据集

文章目录 环境准备准备数据集爬取数据代码删除多余的文件fruit81水果数据图像分类数据集下载统计图像的尺寸和比例分布代码划分训练集和数据集代码可视化图像代码统计各类别的数量 环境准备 推荐按照原作者推荐的环境&#xff1a;代码测试云GPU环境&#xff1a;GPU RTX 3060、…

如何安全、高速、有效地利用IP代理爬取数据

陈老老老板&#x1f9d9;‍♂️ &#x1f46e;‍♂️本文专栏&#xff1a;生活&#xff08;主要讲一下自己生活相关的内容&#xff09;生活就像海洋,只有意志坚强的人,才能到达彼岸。 &#x1f934;本文简述&#xff1a;如何安全、高速、有效地利用IP代理爬取数据 &#x1f473…

多模态AnyGPT——整合图像、语音和文本多模态大规模语言模型算法原理与实践

概述 大规模语言模型在理解和生成人类语言方面具有非凡的能力&#xff0c;但迄今为止&#xff0c;它们的能力主要局限于文本处理。然而&#xff0c;现实世界是一个多模式的环境&#xff0c;信息通过视觉、听觉和触觉等多种感官进行交换。融入这种多样性是开发下一代系统的主要…

云原生Kubernetes: K8S 1.29版本 部署Jenkins

目录 一、实验 1.环境 2.K8S 1.29版本 部署Jenkins 服务 3.jenkins安装Kubernetes插件 二、问题 1.创建pod失败 2.journalctl如何查看日志信息 2.容器内如何查询jenkins初始密码 3.jenkins离线安装中文包报错 4.jenkins插件报错 一、实验 1.环境 &#xff08;1&…

上位机图像处理和嵌入式模块部署(树莓派4b固件功能设计)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 前面我们说过&#xff0c;上位机的功能都是基于插件进行开发的。但是上位机的成本比较贵&#xff0c;一般的企业不一定愿意接接受。这个时候另外一…

【第三版 系统集成项目管理工程师】 十五至尊图

持续更新。。。。。。。。。。。。。。。 【第三版】十五至尊图 十五至尊图【必会】1.整合&#xff08;7&#xff09;2.范围 &#xff08;6&#xff09;3.进度 &#xff08;6&#xff09;4.成本 &#xff08;4&#xff09;5.质量&#xff08;3&#xff09;6.资源&#xff08;6&…

【LeetCode: 3117. 划分数组得到最小的值之和 + 动态规划】

&#x1f680; 算法题 &#x1f680; &#x1f332; 算法刷题专栏 | 面试必备算法 | 面试高频算法 &#x1f340; &#x1f332; 越难的东西,越要努力坚持&#xff0c;因为它具有很高的价值&#xff0c;算法就是这样✨ &#x1f332; 作者简介&#xff1a;硕风和炜&#xff0c;…

Jmeter 测试Dubbo接口-实例

1、Dubbo插件准备 ①把jmeter-plugins-dubbo-2.7.4.1-jar-with-dependencies.jar包放在D:\apache-jmeter-5.5\lib\ext目录 ②重新打开Jmeter客户端 在线程组-添加-取样器-dubbo simple&#xff0c;添加dubbo接口请求 2、Jmeter测试lottery接口 ①配置zookeeper参数 由于dub…

【蓝桥杯嵌入式】蓝桥杯嵌入式第十四届省赛程序真题,真题分析与代码讲解

&#x1f38a;【蓝桥杯嵌入式】专题正在持续更新中&#xff0c;原理图解析✨&#xff0c;各模块分析✨以及历年真题讲解✨都已更新完毕&#xff0c;欢迎大家前往订阅本专题&#x1f38f; &#x1f38f;【蓝桥杯嵌入式】蓝桥杯第十届省赛真题 &#x1f38f;【蓝桥杯嵌入式】蓝桥…

在PostgreSQL中如何处理大对象(Large Objects),例如存储和检索二进制文件?

文章目录 存储二进制文件为大对象步骤 1&#xff1a;创建一个大对象步骤 2&#xff1a;写入数据到大对象 检索大对象为二进制文件步骤 1&#xff1a;打开大对象以进行读取步骤 2&#xff1a;从大对象读取数据 注意事项 PostgreSQL 提供了对大对象&#xff08;Large Objects&…

2024蓝桥杯省赛C++软件算法研究生组题解(含代码)+游记

A题 给你一个音游的游戏记录log.txt&#xff0c;判断玩家的最高连击数 题解 水题&#xff0c;但是要小心&#xff0c;miss的键需要重置k0&#xff0c;超时但正确的键重置k1 个人答案是9 代码&#xff1a; #include<cstdio> #include<cstring> #include<al…

高可用集群——keepalived

目录 1 高可用的概念 2 心跳监测与漂移 IP 地址 3 Keepalived服务介绍 4 Keepalived故障切换转移原理介绍 5 Keepalived 实现 Nginx 的高可用集群 5.1 项目背景 5.2 项目环境 5.3 项目部署 5.3.1 web01\web02配置&#xff1a; 5.3.2nginx负载均衡配置 5.3.3 主调度服…

[lesson43]继承的概念和意义

继承的概念和意义 类之间的组合关系 组合关系的特点 将其他类的对象作为当前类的成员使用当前类的对象与成员对象的生命期相同成员对象在用法上与普通对象完全一致 惊艳的继承 面相对象中的继承指类之间的父子关系 子类拥有父类的所有属性和行为子类就是一种特殊的父类子类…

【C语言】每日一题,快速提升(7)!

&#x1f525;博客主页&#x1f525;&#xff1a;【 坊钰_CSDN博客 】 欢迎各位点赞&#x1f44d;评论✍收藏⭐ 题目&#xff1a;X图形 示例&#xff1a; 输入&#xff1a; 5 //几行数输出&#xff1a; …

日志情况分析

日志是用来记录信息的&#xff0c;更多是方便于查看各种出错信息。时间、日志等级、日志内容、文件名称已经行号都会显示出来&#xff01;日志等级是什么&#xff1f;就是当出问题时&#xff0c;根据问题成都列出等级 Info:常规消息 Debug&#xff1a;t调试信息 Waring:报警信息…