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

2024-04-20 04:36

本文主要是介绍深入探究图像增强(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; // 函数结束
}

次回预告

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

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

感谢您的阅读~

这篇关于深入探究图像增强(C语言实现)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/919299

相关文章

SpringBoot3实现Gzip压缩优化的技术指南

《SpringBoot3实现Gzip压缩优化的技术指南》随着Web应用的用户量和数据量增加,网络带宽和页面加载速度逐渐成为瓶颈,为了减少数据传输量,提高用户体验,我们可以使用Gzip压缩HTTP响应,... 目录1、简述2、配置2.1 添加依赖2.2 配置 Gzip 压缩3、服务端应用4、前端应用4.1 N

SpringBoot实现数据库读写分离的3种方法小结

《SpringBoot实现数据库读写分离的3种方法小结》为了提高系统的读写性能和可用性,读写分离是一种经典的数据库架构模式,在SpringBoot应用中,有多种方式可以实现数据库读写分离,本文将介绍三... 目录一、数据库读写分离概述二、方案一:基于AbstractRoutingDataSource实现动态

Python FastAPI+Celery+RabbitMQ实现分布式图片水印处理系统

《PythonFastAPI+Celery+RabbitMQ实现分布式图片水印处理系统》这篇文章主要为大家详细介绍了PythonFastAPI如何结合Celery以及RabbitMQ实现简单的分布式... 实现思路FastAPI 服务器Celery 任务队列RabbitMQ 作为消息代理定时任务处理完整

Java枚举类实现Key-Value映射的多种实现方式

《Java枚举类实现Key-Value映射的多种实现方式》在Java开发中,枚举(Enum)是一种特殊的类,本文将详细介绍Java枚举类实现key-value映射的多种方式,有需要的小伙伴可以根据需要... 目录前言一、基础实现方式1.1 为枚举添加属性和构造方法二、http://www.cppcns.co

使用Python实现快速搭建本地HTTP服务器

《使用Python实现快速搭建本地HTTP服务器》:本文主要介绍如何使用Python快速搭建本地HTTP服务器,轻松实现一键HTTP文件共享,同时结合二维码技术,让访问更简单,感兴趣的小伙伴可以了... 目录1. 概述2. 快速搭建 HTTP 文件共享服务2.1 核心思路2.2 代码实现2.3 代码解读3.

MySQL双主搭建+keepalived高可用的实现

《MySQL双主搭建+keepalived高可用的实现》本文主要介绍了MySQL双主搭建+keepalived高可用的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,... 目录一、测试环境准备二、主从搭建1.创建复制用户2.创建复制关系3.开启复制,确认复制是否成功4.同

Java实现文件图片的预览和下载功能

《Java实现文件图片的预览和下载功能》这篇文章主要为大家详细介绍了如何使用Java实现文件图片的预览和下载功能,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... Java实现文件(图片)的预览和下载 @ApiOperation("访问文件") @GetMapping("

Java并发编程必备之Synchronized关键字深入解析

《Java并发编程必备之Synchronized关键字深入解析》本文我们深入探索了Java中的Synchronized关键字,包括其互斥性和可重入性的特性,文章详细介绍了Synchronized的三种... 目录一、前言二、Synchronized关键字2.1 Synchronized的特性1. 互斥2.

使用Sentinel自定义返回和实现区分来源方式

《使用Sentinel自定义返回和实现区分来源方式》:本文主要介绍使用Sentinel自定义返回和实现区分来源方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录Sentinel自定义返回和实现区分来源1. 自定义错误返回2. 实现区分来源总结Sentinel自定

Java实现时间与字符串互相转换详解

《Java实现时间与字符串互相转换详解》这篇文章主要为大家详细介绍了Java中实现时间与字符串互相转换的相关方法,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录一、日期格式化为字符串(一)使用预定义格式(二)自定义格式二、字符串解析为日期(一)解析ISO格式字符串(二)解析自定义