深入探究图像增强(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

相关文章

Oracle查询优化之高效实现仅查询前10条记录的方法与实践

《Oracle查询优化之高效实现仅查询前10条记录的方法与实践》:本文主要介绍Oracle查询优化之高效实现仅查询前10条记录的相关资料,包括使用ROWNUM、ROW_NUMBER()函数、FET... 目录1. 使用 ROWNUM 查询2. 使用 ROW_NUMBER() 函数3. 使用 FETCH FI

Python脚本实现自动删除C盘临时文件夹

《Python脚本实现自动删除C盘临时文件夹》在日常使用电脑的过程中,临时文件夹往往会积累大量的无用数据,占用宝贵的磁盘空间,下面我们就来看看Python如何通过脚本实现自动删除C盘临时文件夹吧... 目录一、准备工作二、python脚本编写三、脚本解析四、运行脚本五、案例演示六、注意事项七、总结在日常使用

Java实现Excel与HTML互转

《Java实现Excel与HTML互转》Excel是一种电子表格格式,而HTM则是一种用于创建网页的标记语言,虽然两者在用途上存在差异,但有时我们需要将数据从一种格式转换为另一种格式,下面我们就来看看... Excel是一种电子表格格式,广泛用于数据处理和分析,而HTM则是一种用于创建网页的标记语言。虽然两

Java中Springboot集成Kafka实现消息发送和接收功能

《Java中Springboot集成Kafka实现消息发送和接收功能》Kafka是一个高吞吐量的分布式发布-订阅消息系统,主要用于处理大规模数据流,它由生产者、消费者、主题、分区和代理等组件构成,Ka... 目录一、Kafka 简介二、Kafka 功能三、POM依赖四、配置文件五、生产者六、消费者一、Kaf

使用Python实现在Word中添加或删除超链接

《使用Python实现在Word中添加或删除超链接》在Word文档中,超链接是一种将文本或图像连接到其他文档、网页或同一文档中不同部分的功能,本文将为大家介绍一下Python如何实现在Word中添加或... 在Word文档中,超链接是一种将文本或图像连接到其他文档、网页或同一文档中不同部分的功能。通过添加超

windos server2022里的DFS配置的实现

《windosserver2022里的DFS配置的实现》DFS是WindowsServer操作系统提供的一种功能,用于在多台服务器上集中管理共享文件夹和文件的分布式存储解决方案,本文就来介绍一下wi... 目录什么是DFS?优势:应用场景:DFS配置步骤什么是DFS?DFS指的是分布式文件系统(Distr

NFS实现多服务器文件的共享的方法步骤

《NFS实现多服务器文件的共享的方法步骤》NFS允许网络中的计算机之间共享资源,客户端可以透明地读写远端NFS服务器上的文件,本文就来介绍一下NFS实现多服务器文件的共享的方法步骤,感兴趣的可以了解一... 目录一、简介二、部署1、准备1、服务端和客户端:安装nfs-utils2、服务端:创建共享目录3、服

C#使用yield关键字实现提升迭代性能与效率

《C#使用yield关键字实现提升迭代性能与效率》yield关键字在C#中简化了数据迭代的方式,实现了按需生成数据,自动维护迭代状态,本文主要来聊聊如何使用yield关键字实现提升迭代性能与效率,感兴... 目录前言传统迭代和yield迭代方式对比yield延迟加载按需获取数据yield break显式示迭

Python实现高效地读写大型文件

《Python实现高效地读写大型文件》Python如何读写的是大型文件,有没有什么方法来提高效率呢,这篇文章就来和大家聊聊如何在Python中高效地读写大型文件,需要的可以了解下... 目录一、逐行读取大型文件二、分块读取大型文件三、使用 mmap 模块进行内存映射文件操作(适用于大文件)四、使用 pand

使用SQL语言查询多个Excel表格的操作方法

《使用SQL语言查询多个Excel表格的操作方法》本文介绍了如何使用SQL语言查询多个Excel表格,通过将所有Excel表格放入一个.xlsx文件中,并使用pandas和pandasql库进行读取和... 目录如何用SQL语言查询多个Excel表格如何使用sql查询excel内容1. 简介2. 实现思路3