图像增强

学习

Posted by simplex on June 7, 2021

1. 学会Photoshop软件的基本操作,比如会看直方图、会使用直方图均衡化等。

2.在图像处理中,彩色到灰度转换公式为:𝑔𝑟𝑦 = 0.299 × 𝑟𝑒𝑑 + 0.587 × 𝑔𝑟𝑒𝑒𝑛 + 0.114 × 𝑏𝑙𝑢𝑒,请 使用C/C++编程把彩色图像H0201Rgb.bmp转成为灰度图像H0201Gry.bmp,分别使用查找表和不使 用查找表,分别使用Debug和Release编译,并各执行1000次,比较它们的时间花费(C/C++中的 时间函数为clock_t t=clock())。

MSVCx86 查找表 不使用查找表
debug 1898 1889
release 334 486
MSVCx64    
debug 2059 3368
release 248 526

单位clock tick

3. 使用C/C++编程,对题1得到的灰度图像H0201Gry.bmp进行均值方差规定化,自己设定3组不同的 均值和标准差,保存得到3幅结果图像,请比较它们的不同;在做完均值方差规定化后,再做灰度 范围的线性拉伸,你觉得有意义吗?

avg=128, stddev=100
output1
avg=64, stddev=100
output2
avg=128, stddev=64
output3

当图像的方差较小的时候,整体的图像呈现灰蒙蒙的状态,大家的灰度值都比较接近。当方差变大的时候,亮的地方会特别亮,暗的地方会特别暗。均值体现了图像总体的亮度。

做完在做完均值方差规定化后,再做灰度范围的线性拉伸,没有特别的意义。均值方差规定化的本质就是线性拉伸。

4. 使用C/C++编程,对灰度图像H0202Plane.bmp和H0203Girl.bmp进行直方图均衡化,得到结果图像 H0202Plane_Res.bmp和H0203Girl_Res.bmp,请按照书中分析方法,分析H0202Plane_Res.bmp图 像 的 特 点 ; 比 较 你 做 的 结 果 和 Photoshop 软件做的结果的差异;如果先对灰度图像 H0202Plane.bmp或者H0203Girl.bmp做灰度范围的线性拉伸,再做直方图均衡化,你觉得有意义吗?

c++实现的 PS的
Fig0216Girl_balanced 123123
直方图  
cpp2 PS2
飞机  
Fig0212Plane_balanced PS3
cpp4 PS4

从小女孩那个直方图中可以看出,两边的直方图还是存在着一定的差别,特别是灰度值接近0那里,差别比较明显。上网查了一下,经过经典算法均衡化的图片,最亮的像素值总是255,因为最后一级色阶(255)的百分位一定是100%。而最暗的是由色阶0的数量决定的,像素值不一定是0。发现原来PS在直方图均衡化之后,又做了一步对比度拉伸。将原来区域的值映射回0-255。PS的方法感觉更好,人眼对于暗的东西更加敏感,所以将灰度值映射到0,有利于人更加好的分辨。不过实作了一下,两个直方图还是有一定的区别。PS的直方图拉得更加开,分布更加均匀。

对图像做线性拉伸,在做均衡化,有一定的意义,在一定的情况下。

  • 对于一般情况,是没有意义的,直方图统计的是排名信息,线性变换并不会改变排名信息。
  • 但是对于那些特别亮或者特别暗的像素,通过线性变换,可以让其变得跟比较亮的一样亮,因为线性变换可能让像素的亮度值超过255,最后大家都变成255。导致排名的信息丢失,可能会让整个图像的亮的部分都特别亮,其他不变。
  • 线性变换经过离散化之后,可能会导致排名信息损失

效果图

Fig0216Girl_Affine_balanced

5. 对彩色图像H0201Rgb.bmp进行直方图均衡化是什么效果,使用Photoshop尝试一下;使用C/C++ 编程,尝试编一个彩色图像直方图均衡化的程序,比较与Photoshop软件处理效果的差异,通过实 验猜想Photoshop软件是怎么做的。怎么修改成绩呢?

PS的效果H0201Rgb_PS_balanced

别分对三个通道进行均衡化 对三个通道一起做均衡化 用三个通道的亮度的和来做均衡化
H0201Rgb_balanced_1 H0201Rgb_balanced_balabced2 H0201Rgb_balanced_3

PS 可能是将三个通道一起做归一化。

6.式(2-17)是很多资料中常见的对数变换,但对数变换没有考虑图像中灰度最小值𝑔𝑚𝑖𝑛的问题, 需要在实际应用中进行考虑,你认为如何修改该公式更好?

目标是将像素的亮度值映射\(f\)到0-255的区间

(2-17)已经有\(f(g_{max})=255\)

还需要\(f(g_{min}) = 0\)

所以可以变成 \(G = f(g) = {255 \over{log(1+g_{max}- g_{min} +\epsilon)}}\cdot log(1 + g - g_{min})\)

其中\(\epsilon\)是一个小量防止除以0

7. 使用C/C++编程,对红外热像仪输出的14Bit的原始图像灰度H0204IR14bit.raw(宽度640,高度480, 每个像素的灰度值占2个字节,类型为short int)选择合适的方法转换为8bit的灰度图像,保存为 H0204IR8bit.bmp。(提示:线性拉伸、均值方差规定化、对数变换、直方图均衡化都可以,建议 使用直方图均衡化)

直方图
H0204IR14bit_hist
对数变换
H0204IR14bit_log
对数变换+均值方差规定化(mean=100, std=100)
H0204IR14bit_hist

8.在算法2-3中是可以去掉变量A的,请考虑该如何修改一下语句?

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);
	for (pCur = pGryImg; pCur<pEnd;) histogram[*(pCur++)]++;
	// step.2-------------求LUT[g]-------------------------//
	//A = histogram[0];
	LUT[0] = 255*histogram[0]/(width*height);
	for (g = 1; g<256; g++)
	{
		// A += histogram[g];
        histogram[g] += histogram[g-1]; // <-------用直方图来统计每个灰度值之前有多少像素
		LUT[g] = 255*histogram[g]/(width*height);
	}
	// step.3-------------查表------------------------------//
	for (pCur = pGryImg; pCur<pEnd;) *(pCur++) = LUT[*pCur];
	// step.4-------------结束------------------------------//
	return;
}

9. 假设在教学中,对于学生考试成绩的登记有2种方式,一种是原始分,一种是标准分。有一个班级 的成绩平均分是68,而学校要求成绩的均值为75、标准差为25,那么教师该怎么修改成绩呢?

先现将成绩归一化变成方差为1,均值为0,然后所有人成绩乘25,再+75。借用均值方差规定化的思想。

调试:

  • Windows.h中自带的min,max宏定义令人作呕,于是使用 algorithm中的max , min使用方法(max)(a, b)
  • 修复了一部分存取效率的问题,增加了bmpConverter的拷贝赋值
  • 修复了一些低效率的运算
  • 没有遇到特别的bug,调试耗费时间比较少
  • #define BTYE BYTE 使用了这个无聊的宏来防止自己写错
  • 新增homework.h来保存作业中每个题目的函数
  • delete 指针,对于空指针没有效果,所以不需要判断空指针,直接delete

源代码

// homework.cpp
void hw2_time_cmp()
{
	bmpConverter bmpCvt("./pic/Fig0201.bmp");
	int epoch_num = 1000;
	time_t start = clock();
	while (epoch_num--)
	{
		bmpCvt.RGB2Gry(true, false);
	}
    time_t end = clock() - start;
	cout << "时间: " << end << endl;
	epoch_num = 1000;
    start = clock();
	while (epoch_num--)
	{
		bmpCvt.RGB2Gry(false, false);
	}
    end = clock() - start;
	cout << "时间: " << end << endl;
	bmpCvt.RGB2Gry(true, true);
	bmpCvt.Img2Bmp("./pic/Fig0201Grey.bmp", 8);
}

void hw2_avg_std_conv()
{
	bmpConverter bmpCvt("./pic/Fig0201Grey.bmp");
	bmpCvt.P2avgstd8Bit(128, 64);
	//bmpCvt.PAffine(1.5, 0);
	bmpCvt.Img2Bmp("./pic/output.bmp");
}

void hw2_HistogramEqualize()
{
	bmpConverter bmpCvt("./pic/Fig0212Plane.bmp");
	bmpCvt.PHistogramEqualize8bit();
	bmpCvt.Img2Bmp("./pic/Fig0212Plane_balanced.bmp");
	bmpCvt.BmpFile2Img("./pic/Fig0216Girl.bmp");
	//bmpCvt.PAffine(5, -50);
	bmpCvt.PHistogramEqualize8bit();
	bmpCvt.Img2Bmp("./pic/Fig0216Girl_balanced.bmp");
}


void hw2_24bitHist()
{
	bmpConverter bmpCvt("./pic/H0201Rgb.bmp");
	bmpCvt.PHistogramEqualize24bit2();
	bmpCvt.Img2Bmp("./pic/H0201Rgb_balanced.bmp");
}


void hw2_14bit_convert()
{
	bmpConverter bmpCvt;
	bmpCvt.read14bitRaw("./pic/H0204IR14bit.raw");
	puts("转化完成");
	bmpCvt.Img2Bmp("./pic/H0204IR14bit.bmp", 8);
}

// bmpConverter.h
#pragma once
#include <Windows.h>
#include <cstring>
#include <cstdio>
#include "switcher.h"
#include <iostream>
class bmpConverter
{
public:
	bmpConverter(const char * orgfile);
	bmpConverter();
	// 灰度图像反转
	void InvertImg();
	// 读取bmp
	bool BmpFile2Img(const char * DstFile);
	// 保存bmp
	bool Img2Bmp(const char * DstFile, int bitCnt=0, char RGBMOD='\0');
	// 读取14bit raw数据,已经写死
	bool read14bitRaw(const char * DstFile);
	//RGB转灰度图
	void RGB2Gry(bool table_chk=true, bool inplace=true);
	// 线性拉伸(仿射变换)
	void PAffine(double k, double b);
	// 均值标准差规定化
	void P2avgstd8Bit(double mean = 128.0, double stddev = 1);
	// 8bit直方图标准化实现
	void PHistogramEqualize8bit();
	// 24bit直方图标准化实现(三个通道分别实现)
	void PHistogramEqualize24bit();
	// 24bit直方图标准化实现(三个通道在一起实现)
	void PHistogramEqualize24bit1();
	// 24bit直方图标准化实现(三个通道的均值实现)
	void PHistogramEqualize24bit2();
	~bmpConverter();
	bmpConverter(bmpConverter &);
	bmpConverter(bmpConverter &&);
private:
	void PHistogramEqualize14bit(short * pRawImg);
	void PHistogramEqualize14bit2(short * pRawImg);
	void getHistGram8bit(int * hist);
	void getHistGram24bitavg(int * hist);
	void getHistGram24bit(int * hist);
	bool Img28bitBmp(const char * DstFile, char mod);
	bool Img224bitBmp(const char * DstFile);
	BYTE * pImg=nullptr;
	long width, height;
	BITMAPFILEHEADER FileHeader;
	BITMAPINFOHEADER BmpHeader;
	int channel;

};





//bmpConverter.cpp
//________homework2的代码__________________________

// 14bit直方图归一化
void bmpConverter::PHistogramEqualize14bit(short * pRawImg)
{
	int hist[1 << 14], sum = width * height, A = 0;
	pImg = new BYTE[sum];
	BYTE LUT[1 << 14], * pCur = pImg;
	// 得到直方图
	short * spCur = pRawImg, *spDes = pRawImg + sum;
	for (int i = 0; i < (1 << 14); i++) hist[i] = 0;
	while (spCur < spDes) hist[*(spCur++)]++;
	for (int i = 0; i < (1 << 14); i++)LUT[i] = 255 * (A += hist[i]) / sum;
	for (spCur = pRawImg; spCur < spDes;)*(pCur++) = LUT[*(spCur++)];

}
// 14bit直方图归一化2
void bmpConverter::PHistogramEqualize14bit2(short * pRawImg)
{
	int hist[1 << 14], sum = width * height, A = 0;
	pImg = new BYTE[sum];
	BYTE LUT[1 << 14], *pCur = pImg;
	// 得到直方图
	short * spCur = pRawImg, *spDes = pRawImg + sum;
	for (int i = 0; i < (1 << 14); i++) hist[i] = 0;
	while (spCur < spDes) hist[*(spCur++)]++;
	int maxp = (1 << 14) - 1, minp = 0;// 求最小灰度和最大灰度
	while (!hist[minp])minp++;
	while (!hist[maxp])maxp--;
	double c = 255 / log(1 + maxp - minp + 1e-8);
#ifdef DEBUG
	printf("gmax : %d   gmin : %d   c %llf\n", maxp, minp, c);
#endif // DEBUG

	for (int i = minp; i <= maxp; i++)LUT[i] = c * log(1 + i - minp);
	for (spCur = pRawImg; spCur < spDes;)*(pCur++) = LUT[*(spCur++)];
	P2avgstd8Bit(100, 100);
}

// 读取14bit raw数据,已经写死
bool bmpConverter::read14bitRaw(const char * DstFile)
{
	delete pImg;

	width = 640, height = 480, channel = 1;
	BmpHeader.biBitCount = 8;
	FILE *fp;
	int err = fopen_s(&fp, DstFile, "rb"); // "./pic/H0204IR14bit.raw"
	if (err)
	{
		printf("file open err %d \n", err);
		return false;
	}
	short * pRawImg = new short[width*height];
	fread(pRawImg, sizeof(short), width * height, fp);
	fclose(fp);
	PHistogramEqualize14bit2(pRawImg);
	return true;
}



// RGB转灰度图
void bmpConverter::RGB2Gry(bool table_chk, bool inplace)
{
	if (channel != 3 || !pImg)return;
	int sum = width * height;
	BYTE * temp_save = new BYTE[sum];
	if (!temp_save)
	{
		puts("程序错误,内存申请失败");
		return;
	}
#ifdef DEBUG
	//cout << (int)(temp_save) <<"113" <<sizeof(temp_save)<< endl;
	////cout << (int)pImg << endl;
#endif // DEBUG
	BYTE * p1 = temp_save, *p2 = pImg - 1;
	if (table_chk)
	{
		BYTE r8[256], g8[256], b8[256];
		int r_ratio = 0.299 * 1024, g_ratio = 0.587 * 1024;
		for (int i = 0; i < 256; i++)
		{
			r8[i] = r_ratio * i >> 10;
			g8[i] = g_ratio * i >> 10;
			b8[i] = i - r8[i] - g8[i];
		}
		while (sum--)*(p1++) = (b8[*(++p2)] + g8[*(++p2)] + r8[*(++p2)]);//blue,green,red
	}
	else
	{
		while (sum--)*(p1++) = ((*(++p2)) * 0.114 + 0.587 * (*(++p2)) + 0.299 * (*(++p2)));//blue,green,red
	}
	if (inplace)
	{
		swap(temp_save, pImg);
		channel = 1;
	}

#ifdef DEBUG
	//cout << int(temp_save) << endl;
	//cout << (int)pImg << endl;
#endif // DEBUG
	delete temp_save;
	return;
}

// 线性拉伸(仿射变换)
void bmpConverter::PAffine(double k, double b)
{
	if (!pImg)return;
	BYTE LUT[256], * pCur = pImg, *pDes = pImg + width * height * channel;
	for (int i = 0; i < 256; i++)LUT[i] = (std::min)(255.0, (std::max)(0.0, k * i + b));
	while (pCur < pDes)*(pCur++) = LUT[*pCur];


}
// 8bit均值标准差规定化
void bmpConverter::P2avgstd8Bit(double mean, double stddev)
{
	if (BmpHeader.biBitCount != 8)return;
	int tot = width * height;
	int hist[256];
	getHistGram8bit(hist);
	double avg, k ,b, sdev = 0;
	if (tot > 16777210)
	{
		unsigned long long  sum = 0;
		for (int i = 0; i < 256; i++) sum += hist[i] * i;
		avg = double(sum) / tot;
	}
	else
	{
		unsigned int sum = 0; 
		for (int i = 0; i < 256; i++) sum += hist[i] * i;
		avg = double(sum) / tot;
	}
	for (int i = 0; i < 256; i++) sdev += hist[i] * (i - avg) * (i - avg);
	k = stddev / sqrt(sdev / (tot - 1));
	b = mean - avg * k;
	PAffine(k, b);
	return;
}
// 得到8bit的直方图
void bmpConverter::getHistGram8bit(int * hist)
{
	for (int i = 0; i < 256; i++)hist[i] = 0;
	for (BYTE * pCur = pImg, *pDest = pImg + width * height;pCur < pDest;)
		hist[*(pCur++)]++;
}
// 8bit直方图标准化实现
void bmpConverter::PHistogramEqualize8bit()
{
	if (BmpHeader.biBitCount != 8)
	{ 
		puts("图像位数错误"); return; 
	}
	int hist[256], LUT[256], A = 0, sum = width * height;
	getHistGram8bit(hist);
	for (int i = 0, A = hist[1]; i < 256; i++)LUT[i] = 255 * (A += hist[i]) / sum;
	for (BYTE * pCur = pImg, *pDest = pImg + width * height; pCur < pDest;)
		*(pCur++) = LUT[*pCur];

}
// 得到24bit直方图
void bmpConverter::getHistGram24bit(int * hist)
{
	for (int i = 0; i < 256*3; i++)hist[i] = 0;
	for (BYTE * pCur = pImg, p = 0, *pDest = pImg + width * height * 3; pCur < pDest; p = (p + 1)%3)
		hist[*(pCur++) * 3 + p]++;
}
// 24bit直方图标准化实现(三个通道分别实现)
void bmpConverter::PHistogramEqualize24bit()
{
	if (BmpHeader.biBitCount != 24)
	{
		puts("图像位数错误"); return;
	}
	int hist[256 * 3], LUTR[256], LUTG[256], LUTB[256], sum = width * height, AR=0, AG=0, AB=0;
	getHistGram24bit(hist);
	for (int i = 0, p=0; i < 256; i++)
	{
		LUTB[i] = 255 * (AB += hist[p++]) / sum;//blue,green,red
		LUTG[i] = 255 * (AG += hist[p++]) / sum;//blue,green,red
		LUTR[i] = 255 * (AR += hist[p++]) / sum;//blue,green,red
	}
	for (BYTE * pCur = pImg, *pDest = pImg + width * height * channel; pCur < pDest;)
	{
		*(pCur++) = LUTB[*pCur];//blue,green,red
		*(pCur++) = LUTG[*pCur];//blue,green,red
		*(pCur++) = LUTR[*pCur];//blue,green,red
	}
		


}
// 24bit直方图标准化实现(三个通道在一起实现)
void bmpConverter::PHistogramEqualize24bit1()
{
	if (BmpHeader.biBitCount != 24)
	{
		puts("图像位数错误"); return;
	}
	int hist[256 * 3], LUTR[256], LUTG[256], LUTB[256], sum = width * height * channel, A = 0;
	//for (; !hist[minp]; minp++);
	//minp /= 3;
	getHistGram24bit(hist);
	for (int i = 0, p = 0; i < 256;i++)
	{
		LUTB[i] = 255 * (A += hist[p++]) / sum;//blue,green,red
		//LUTB[i] = (LUTB[i] - minp) * 255 / (255 - minp);//blue,green,red
		LUTG[i] = 255 * (A += hist[p++]) / sum;//blue,green,red
		//LUTG[i] = (LUTG[i] - minp) * 255 / (255 - minp); 
		LUTR[i] = 255 * (A += hist[p++]) / sum;//blue,green,red
		//LUTR[i] = (LUTR[i] - minp) * 255 / (255 - minp); 
	}
	for (BYTE * pCur = pImg, *pDest = pImg + width * height * channel; pCur < pDest;)
	{
		*(pCur++) = LUTB[*pCur];//blue,green,red
		*(pCur++) = LUTG[*pCur];//blue,green,red
		*(pCur++) = LUTR[*pCur];//blue,green,red
	}
}
// 得到24bit的平均直方图
void bmpConverter::getHistGram24bitavg(int * hist)
{
	for (int i = 0; i < 256; i++)hist[i] = 0;
	for (BYTE * pCur = pImg, *pDest = pImg + width * height * 3; pCur < pDest;)
		hist[(*(pCur++) + *(pCur++) + *(pCur++)) / 3]++;

}
// 24bit直方图标准化实现(三个通道的均值实现)
void bmpConverter::PHistogramEqualize24bit2()
{
	if (BmpHeader.biBitCount != 24)
	{
		puts("图像位数错误"); return;
	}
	int hist[256], LUT[256], sum = width * height * channel, A = 0;
	getHistGram24bitavg(hist);
	for (int i = 0; i < 256; i++)LUT[i] = 255 * (A += hist[i]) / sum;
	for (BYTE * pCur = pImg, *pDest = pImg + width * height * channel; pCur < pDest;)
		*(pCur++) = LUT[*pCur];
}