题目:分析图 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.非线性动态范围调整的方法
最终,我们找到并使用非线性动态范围调整的方法,可以用光滑的曲线来实现图像像素的改变。采用该方法的原因是,线性动态范围调整的分段线性影射不够光滑,考虑到人眼对视觉信号的处理过程中,有一个近似对数算子的环节,因此,可采用对数运算来实现非线性动态范围调整。
设原图为,通过非线性动态范围调整方法处理后的图像为,则二者的映射关系如下:
其中,。
执行如下算法代码。
/*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;
}