图像增强 亮度恢复 亮度自适应增强 低亮度调整 *4【C++】

题目:分析图 3 所示的图像存在什么问题,请用 C 语言编程设计一种图像处理方法解决 该问题,给出算法的关键代码和实验结果。(20 分)

该图像是一张彩色图像,但是人眼观察整体较暗,其原因是因为拍摄成像时光照不足,所以需要通过图像增强手段进行光照补足。

1.直方图均衡化

首先,我想到可以使用直方图均衡化的算法,运用变换来实现调整图像的灰度级分布,以此实现增强、改善图像的对比度目的。我们通过把RGB图像三个通道的灰度值分别进行直方图均衡化,将图像从较暗区域扩展至0-255均匀分布,这样就可以达到增强图像整体对比度的效果,恢复图像光照。

直方图均衡化的步骤可以总结如下图。

因为课堂中已经进行过均衡化的实验,我们在此处只是拓展到RGB3个通道上,所以可复用如下代码读取bmp图像,并通过直方图统计、计算累加直方图并完成映射、像素填充三个步骤,进行彩色直方图的均衡化。

/*round函数*/
int round1(double f){
	if(int(f) + 0.5 > f)
		return int(f);
	else
		return int(f) + 1; 
}

/*彩色图像直方图均衡方法*/
void hist(const char *path, const char *path2)
{
	FILE *fo;			//打开bmp文件
	FILE *fs;			//存储bmp文件
	
	BITMAPFILEHEADER FileHeader;		//bmp的文件头 
	BITMAPINFOHEADER InfoHeader;		//bmp的信息头 
	
	fo = fopen(path, "rb+");			//读bmp文件 
	fs = fopen(path2, "wb+");			//写bmp文件 
	
	fread(&FileHeader, 1, sizeof(BITMAPFILEHEADER), fo);		//把bmp文件头读出 
	fread(&InfoHeader, 1, sizeof(BITMAPINFOHEADER), fo);		//把bmp信息头读出 
	fwrite(&FileHeader, 1, sizeof(BITMAPFILEHEADER), fs);		//把bmp文件头写入 
	fwrite(&InfoHeader, 1, sizeof(BITMAPINFOHEADER), fs);		//把bmp信息头写入 
	
	if(InfoHeader.biBitCount == 24){					//当为彩色图像时 
		
		int width = InfoHeader.biWidth;			//位图的宽度 
		int height = InfoHeader.biHeight;		//位图的高度 
		int width3 = width * 3;				//BGR三通道所以宽度需要乘以3 
		int count = InfoHeader.biBitCount;
		int lb = (count * width / 8 + 3) / 4 * 4;
		
		unsigned char *PixDataBmp = new unsigned char [lb * height];		//存bmp数据的空间  
		fread(PixDataBmp, 1, lb * height, fo);		//从bmp文件中读取数据
		
		//直方图均衡化:
		
		//1.直方图统计 
		double s1_R[256] = {0};		//存储图像灰度值的数组
		double s1_G[256] = {0};		//存储图像灰度值的数组
		double s1_B[256] = {0};		//存储图像灰度值的数组
		int s2_R[256] = {0};			//存储直方图均衡化后的灰度值的数组
		int s2_G[256] = {0};			//存储直方图均衡化后的灰度值的数组
		int s2_B[256] = {0};			//存储直方图均衡化后的灰度值的数组
		int m_R, m_G, m_B;
		
		for(int i = 0; i < height; i++){
			for(int j = 0; j < width; j++){
				m_B = *(PixDataBmp + i * lb + j * 3);
				s1_B[m_B]++;
				m_G = *(PixDataBmp + i * lb + j * 3 + 1);
				s1_G[m_G]++;
				m_R = *(PixDataBmp + i * lb + j * 3 + 2);
				s1_R[m_R]++;
			}
		}
		
		//2.计算累加直方图并完成映射
		s1_R[0] = s1_R[0] / (width * height);
		s2_R[0] = round1(double((256 - 1) * s1_R[0]));
		s1_G[0] = s1_G[0] / (width * height);
		s2_G[0] = round1(double((256 - 1) * s1_G[0]));
		s1_B[0] = s1_B[0] / (width * height);
		s2_B[0] = round1(double((256 - 1) * s1_B[0]));
		cout<<"B原灰度级:"<<0<<",现灰度级:"<<s2_B[0]<<endl;
		cout<<"G原灰度级:"<<0<<",现灰度级:"<<s2_G[0]<<endl;
		cout<<"R原灰度级:"<<0<<",现灰度级:"<<s2_R[0]<<endl;
		for(int i = 1; i < 256; i++){
			s1_R[i] = s1_R[i] / (width * height);
			s1_R[i] = s1_R[i] + s1_R[i-1];
			s2_R[i] = int(round1(double((256 - 1) * s1_R[i])));
			s1_B[i] = s1_B[i] / (width * height);
			s1_B[i] = s1_B[i] + s1_B[i-1];
			s2_B[i] = int(round1(double((256 - 1) * s1_B[i])));
			s1_G[i] = s1_G[i] / (width * height);
			s1_G[i] = s1_G[i] + s1_G[i-1];
			s2_G[i] = int(round1(double((256 - 1) * s1_G[i])));
			cout<<"B原灰度级:"<<i<<",现灰度级:"<<s2_B[i]<<endl;
			cout<<"G原灰度级:"<<i<<",现灰度级:"<<s2_G[i]<<endl;
			cout<<"R原灰度级:"<<i<<",现灰度级:"<<s2_R[i]<<endl;
		}
		
		//3.像素填充
		unsigned char *PixDataBmp2 = new unsigned char [lb * height];		//存bmp数据的空间 
		for(int i = 0; i < height; i++){
			for(int j = 0; j < width; j++){
				m_B = *(PixDataBmp + i * lb + j * 3);
				*(PixDataBmp2 + i * lb + j * 3) = s2_B[m_B];
				m_G = *(PixDataBmp + i * lb + j * 3 + 1);
				*(PixDataBmp2 + i * lb + j * 3 + 1) = s2_G[m_G];
				m_R = *(PixDataBmp + i * lb + j * 3 + 2);
				*(PixDataBmp2 + i * lb + j * 3 + 2) = s2_R[m_R];
			}
		}
		
		fwrite(PixDataBmp2, 1 , lb * height , fs);		//写直方图均衡化后的数据
		
		free(PixDataBmp);			//释放空间
		free(PixDataBmp2);			//释放空间
		
		cout<<"彩色bmp图像直方图均衡化成功!"<<endl<<endl; 

	}
	
	fclose(fo);			//关闭文件 
	fclose(fs);
}

然而通过3通道的直方图均衡化我们可以得到下面这样一张结果图像。从该图像中可以看出,虽然亮度有被明显提亮,但是出现色彩缺损和明显色块问题,效果十分灾难。

通过图像的数据分析,由于出现太多0、1的灰度级,导致在映射时很容易被映射到较大的同一灰度级上,导致上述两个问题产生。

2.色反相乘

为了提升光照恢复效果,我们采用绿色色反并分别与RGB通道相乘,将新得到的图层与原图层做滤色混合,即执行f(a, b)=1-(1-a)*(1-b)的操作,从而实现照度增强,具体代码如下。

/*色反方法*/
void fan(const char *path, const char *path2)
{
	FILE *fo;			//打开bmp文件
	FILE *fs;			//存储bmp文件
	
	BITMAPFILEHEADER FileHeader;		//bmp的文件头 
	BITMAPINFOHEADER InfoHeader;		//bmp的信息头 
	
	fo = fopen(path, "rb+");			//读bmp文件 
	fs = fopen(path2, "wb+");			//写bmp文件 
	
	fread(&FileHeader, 1, sizeof(BITMAPFILEHEADER), fo);		//把bmp文件头读出 
	fread(&InfoHeader, 1, sizeof(BITMAPINFOHEADER), fo);		//把bmp信息头读出 
	fwrite(&FileHeader, 1, sizeof(BITMAPFILEHEADER), fs);		//把bmp文件头写入 
	fwrite(&InfoHeader, 1, sizeof(BITMAPINFOHEADER), fs);		//把bmp信息头写入 
	
	if(InfoHeader.biBitCount == 24){					//当为彩色图像时 
		
		int width = InfoHeader.biWidth;			//位图的宽度 
		int height = InfoHeader.biHeight;		//位图的高度 
		int width3 = width * 3;				//BGR三通道所以宽度需要乘以3 
		int count = InfoHeader.biBitCount;
		int lb = (count * width / 8 + 3) / 4 * 4;
		
		unsigned char *PixDataBmp = new unsigned char [lb * height];		//存bmp数据的空间  
		fread(PixDataBmp, 1, lb * height, fo);		//从bmp文件中读取数据
		
		int g_alpha, r1, g1, b1, r2, g2, b2;
		
		unsigned char *PixDataBmp2 = new unsigned char [lb * height];		//存bmp数据的空间 
		for(int i = 0; i < height; i++){
			for(int j = 0; j < width; j++){
				g_alpha = 255 - *(PixDataBmp + i * lb + j * 3 + 1);
				
				b1 = *(PixDataBmp + i * lb + j * 3);
				b2 = b1 * g_alpha >> 8;
				*(PixDataBmp2 + i * lb + j * 3) = (255 - ((255 - (b1))*(255 - (b2)) >> 8));
				
				g1 = *(PixDataBmp + i * lb + j * 3 + 1);
				g2 = g1 * g_alpha >> 8;
				*(PixDataBmp2 + i * lb + j * 3 + 1) = (255 - ((255 - (g1))*(255 - (g2)) >> 8));
				
				r1 = *(PixDataBmp + i * lb + j * 3 + 2);
				r2 = r1 * g_alpha >> 8;
				*(PixDataBmp2 + i * lb + j * 3 + 2) = (255 - ((255 - (r1))*(255 - (r2)) >> 8));
			}
		}
		
		fwrite(PixDataBmp2, 1 , lb * height , fs);		//写直方图均衡化后的数据
		
		free(PixDataBmp);			//释放空间
		free(PixDataBmp2);			//释放空间
		
		cout<<"彩色bmp图像反色处理成功!"<<endl<<endl; 

	}
	
	fclose(fo);			//关闭文件 
	fclose(fs);
}

该方法得到的结果如下图所示,可以看到,该方法只能实现一定的亮度增强,但是效果不是很明显,对暗处的改善效果几乎没有。

3.转成灰度图恢复

于是我换另一种思路,将RGB三通道的值,通过式子(0.299f * r + 0.587f * g + 0.114f * b)转换成一通道的灰度图像,然后通过全局自适应原理的公式:

实现传统方法对灰度图像的亮度恢复,具体的实现代码如下。

/*灰度恢复方法*/
void light(const char *path, const char *path2)
{
	FILE *fo;			//打开bmp文件
	FILE *fs;			//存储bmp文件
	
	BITMAPFILEHEADER FileHeader;		//bmp的文件头 
	BITMAPINFOHEADER InfoHeader;		//bmp的信息头 
	
	fo = fopen(path, "rb+");			//读bmp文件 
	fs = fopen(path2, "wb+");			//写bmp文件 
	
	fread(&FileHeader, 1, sizeof(BITMAPFILEHEADER), fo);		//把bmp文件头读出 
	fread(&InfoHeader, 1, sizeof(BITMAPINFOHEADER), fo);		//把bmp信息头读出 
	fwrite(&FileHeader, 1, sizeof(BITMAPFILEHEADER), fs);		//把bmp文件头写入 
	fwrite(&InfoHeader, 1, sizeof(BITMAPINFOHEADER), fs);		//把bmp信息头写入 
	
	if(InfoHeader.biBitCount == 24){					//当为彩色图像时 
		
		int width = InfoHeader.biWidth;			//位图的宽度 
		int height = InfoHeader.biHeight;		//位图的高度 
		int width3 = width * 3;				//BGR三通道所以宽度需要乘以3 
		int count = InfoHeader.biBitCount;
		int lb = (count * width / 8 + 3) / 4 * 4;
		
		unsigned char *PixDataBmp = new unsigned char [lb * height];		//存bmp数据的空间  
		fread(PixDataBmp, 1, lb * height, fo);		//从bmp文件中读取数据
		
		float Lwmax, Lwaver, sum=0; 
		int r, g, b; 
		
		unsigned char *PixDataBmp2 = new unsigned char [lb * height];		//存bmp数据的空间 
		for(int i = 0; i < height; i++){
			for(int j = 0; j < width; j++){
				
				b = *(PixDataBmp + i * lb + j * 3);
				g = *(PixDataBmp + i * lb + j * 3 + 1);
				r = *(PixDataBmp + i * lb + j * 3 + 2);
				
				float Lw = 0.299f * r + 0.587f * g + 0.114f * b;
 				sum += log(0.001f + Lw);
 				if (Lw > Lwmax){
					Lwmax = Lw;
				}
			}
		}
		sum /= height * width;
		Lwaver = exp(sum);
		
		for(int i = 0; i < height; i++){
			for(int j = 0; j < width; j++){
				
				b = *(PixDataBmp + i * lb + j * 3);
				g = *(PixDataBmp + i * lb + j * 3 + 1);
				r = *(PixDataBmp + i * lb + j * 3 + 2);
				
				float Lw = 0.299f * r + 0.587f * g + 0.114f * b;
 				float Lg = log(Lw / Lwaver + 1) / log(Lwmax / Lwaver + 1);
 				float gain = Lg / Lw;
 				
 				*(PixDataBmp2 + i * lb + j * 3) = int(b * gain * 255);
				*(PixDataBmp2 + i * lb + j * 3 + 1) = int(g * gain * 255);
				*(PixDataBmp2 + i * lb + j * 3 + 2) = int(r * gain * 255);
 				
 				
			}
		}
		
		fwrite(PixDataBmp2, 1 , lb * height , fs);		//写直方图均衡化后的数据
		
		free(PixDataBmp);			//释放空间
		free(PixDataBmp2);			//释放空间
		
		cout<<"彩色bmp图像灰度恢复处理成功!"<<endl<<endl; 

	}
	
	fclose(fo);			//关闭文件 
	fclose(fs);
}

该算法的结果已经是目前看来效果最好的一个了,它不仅提升了亮度还能保留原有的部分色彩。但是该算法还存在明显的点状色块问题。

4.非线性动态范围调整的方法

最终,我们找到并使用非线性动态范围调整的方法,可以用光滑的曲线来实现图像像素的改变。采用该方法的原因是,线性动态范围调整的分段线性影射不够光滑,考虑到人眼对视觉信号的处理过程中,有一个近似对数算子的环节,因此,可采用对数运算来实现非线性动态范围调整。

设原图为f(i,j),通过非线性动态范围调整方法处理后的图像为g(i,j),则二者的映射关系如下:

g(i,j) = clog_{10}(1+f(i,j))

其中,c=255/lg256

执行如下算法代码。

/*log恢复方法*/
void log(const char *path, const char *path2)
{
	FILE *fo;			//打开bmp文件
	FILE *fs;			//存储bmp文件
	
	BITMAPFILEHEADER FileHeader;		//bmp的文件头 
	BITMAPINFOHEADER InfoHeader;		//bmp的信息头 
	
	fo = fopen(path, "rb+");			//读bmp文件 
	fs = fopen(path2, "wb+");			//写bmp文件 
	
	fread(&FileHeader, 1, sizeof(BITMAPFILEHEADER), fo);		//把bmp文件头读出 
	fread(&InfoHeader, 1, sizeof(BITMAPINFOHEADER), fo);		//把bmp信息头读出 
	fwrite(&FileHeader, 1, sizeof(BITMAPFILEHEADER), fs);		//把bmp文件头写入 
	fwrite(&InfoHeader, 1, sizeof(BITMAPINFOHEADER), fs);		//把bmp信息头写入 
	
	if(InfoHeader.biBitCount == 24){					//当为彩色图像时 
		
		int width = InfoHeader.biWidth;			//位图的宽度 
		int height = InfoHeader.biHeight;		//位图的高度 
		int width3 = width * 3;				//BGR三通道所以宽度需要乘以3 
		int count = InfoHeader.biBitCount;
		int lb = (count * width / 8 + 3) / 4 * 4;
		
		unsigned char *PixDataBmp = new unsigned char [lb * height];		//存bmp数据的空间  
		fread(PixDataBmp, 1, lb * height, fo);		//从bmp文件中读取数据
		
		float c = 105.8865; 
		int b, g, r;
		
		unsigned char *PixDataBmp2 = new unsigned char [lb * height];		//存bmp数据的空间 
		for(int i = 0; i < height; i++){
			for(int j = 0; j < width; j++){
				
				b = *(PixDataBmp + i * lb + j * 3);
				g = *(PixDataBmp + i * lb + j * 3 + 1);
				r = *(PixDataBmp + i * lb + j * 3 + 2);
				*(PixDataBmp2 + i * lb + j * 3) = int(c * log10(1 + b));
				*(PixDataBmp2 + i * lb + j * 3 + 1) = int(c * log10(1 + g));
				*(PixDataBmp2 + i * lb + j * 3 + 2) = int(c * log10(1 + r));
			}
		}
		
		fwrite(PixDataBmp2, 1 , lb * height , fs);		//写直方图均衡化后的数据
		
		free(PixDataBmp);			//释放空间
		free(PixDataBmp2);			//释放空间
		
		cout<<"彩色bmp图像灰度恢复处理成功!"<<endl<<endl; 

	}
	
	fclose(fo);			//关闭文件 
	fclose(fs);
}

最终我们可以得到一种比较好的还原该夜间图像照明的图像结果。

虽然题目中希望设计一种算法解决该问题,但是我在不断思考和尝试探索中,不断寻找更好的图像增强效果,最终使用非线性动态范围调整的方法实现夜间图像的光照增强。

其余部分代码

#include <iostream>
#include <fstream>
#include <malloc.h>
#include <stdio.h>
#include<cmath>
#include <string.h>
#pragma pack(1)					//设定变量以n字节对齐方式

using namespace std;

typedef unsigned char BYTE;		//1个字节
typedef unsigned short WORD;	//2个字节
typedef unsigned int DWORD;		//4个字节
typedef unsigned long LONG;		//4个字节

/*位图文件头定义 14个字节*/
typedef struct tagBITMAPFILEHEADER {
    WORD bfType;				//位图文件的类型,必须为BM(0-1字节)
    DWORD bfSize;				//位图文件的大小,以字节为单位(2-5字节) 
    WORD bfReserved1;			//位图文件保留字,必须为0(6-7字节)
    WORD bfReserved2;			//位图文件保留字,必须为0(8-9字节)
    DWORD bfOffBits;			//位图数据的起始位置,以相对于位图(10-13字节)
} BITMAPFILEHEADER;

/*位图信息头定义 40字节*/
typedef struct tagBITMAPINFOHEADER {
    DWORD biSize;				//本结构所占用字节数(14-17字节) 
    LONG biWidth;				//位图的宽度,以像素为单位(18-21字节) 
    LONG biHeight;				//位图的高度,以像素为单位(22-25字节) 
    WORD biPlanes;				//目标设备的级别,必须为1(26-27字节) 
    WORD biBitCount;			//每个像素所需的位数,必须是1(双色)、4(16色)、8(256色)、24(真彩色),(28-29字节)
    DWORD biCompression;		//位图压缩类型,必须是0(不压缩)、1(BI_RLE8压缩类型)、2(BI_RLE4压缩类型),(30-33字节)
    DWORD biSizeImage;			//位图的大小,以字节为单位(34-37字节)
    LONG biXPelsPerMeter;		//位图水平分辨率,每米像素数(38-41字节)
    LONG biYPelsPerMeter;		//位图垂直分辨率,每米像素数(42-45字节)
    DWORD biClrUsed;			//位图实际使用的颜色表中的颜色数(46-49字节)
    DWORD biClrImportant;		//位图中重要的色彩数,如果该值为零,则认为所有的颜色都是重要的(50-53字节)
} BITMAPINFOHEADER; 

/*调色板定义 4字节*/
typedef struct tagRGBQUAD {
    BYTE rgbBlue;				//蓝色的亮度(值范围为0-255)
    BYTE rgbGreen;				//绿色的亮度(值范围为0-255)
    BYTE rgbRed;				//红色的亮度(值范围为0-255)
    BYTE rgbReserved;			//保留,必须为0 
} RGBQUAD;

int main()
{
    const char *path11 = "2.bmp";				//彩色图像地址 
    const char *path12 = "2_hist.bmp";			//直方图均衡后的彩色图像地址 
    const char *path13 = "2_fanse.bmp";			//色反后的彩色图像地址 
    const char *path14 = "2_light.bmp";			//亮度恢复彩色图像地址 
    const char *path15 = "2_log.bmp";			//亮度恢复彩色图像地址
    
	cout<<"彩色图像直方图均衡:"<<endl; 
	hist(path11, path12); 
	cout<<"彩色图像色反复明:"<<endl; 
	fan(path11, path13); 
	cout<<"彩色图像灰度恢复:"<<endl; 
	light(path11, path14); 
	cout<<"彩色图像log恢复:"<<endl; 
	log(path11, path15); 
	system("pause");

	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

秋千的千秋

希望你喜欢

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值