C/C++图像处理1 灰度图修正

写在前面

非专业做图像处理的,如有错漏,敬请指正。
这是课程作业,对我这种之前没用过C语言图像处理的人来说很费劲。在此分享一是做学习记录,二是希望为遇到此类问题的朋友提供一种思路和方法;但如果有同样作业的同学看到此文,不建议直接下载源码抄作业,没意义,建议看懂原理之后自己敲处理核心部分代码、改改界面框架。所以这个没有百度云免费分享,而且我这个做得也很low,如果真的有学习需要的,希望能帮到:
gray-level_correction_noel202007.zip
需要用到teechart控件,没有的朋友看这里:VS2013 简单MFC应用以及teechart使用方法
若出现以下错误(没出现就不管)
在这里插入图片描述
那么点击项目属性,更改平台工具集
在这里插入图片描述

内容

搭建一个图像处理程序框架,实现将图片像素读取至二维数组,对图片的打开、显示、保存等基本功能,并做灰度修正处理。图像处理的部分是c写的,主要是将图片读入二维数组,然后对每个像素的灰度值进行操作,c++主要是用于搭建处理程序框架;
处理内容是利用分段折线变换关系对灰度图像进行修正,显示、保存变换结果和直方图;
图片显示使用picture控件,直方图用的teechart绘制。
参考书目:share_noel/图像处理/数字图像处理-夏良正.pdf
https://blog.csdn.net/qq_41102371/article/details/125646840
灰度变换原理(教材P140):
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
灰度变换结果:前面两张待处理原图在此:share_noel/图像处理/lena.bmp(lena1.bmp) 提取码:0ooc
第一行第一张是lena.bmp原图,后面4张是lena.bmp的分段折线变换图,第二行第一张是lena1原图,后面4张是lena1.bmp的分段折线变换图。左下角是直方图,可通过下拉框选择显示。在这里插入图片描述

bmp图片文件的读写显示

这里涉及bmp图片格式及灰度图片转换,参考这几篇博客:
c语言数字图像处理(一):bmp图片格式及灰度图片转换
BMP图片结构解析(文中是用UltraEdit软件打开的BMP文件,UltraEdit的安装推荐微信公众号“软件安装管家”)
BMP文件结构
BMP图片文件读取及灰度图转化代码参考了一个cnblogs的博主,推荐阅读,这是他的主页https://www.cnblogs.com/GoldBeetle/

处理程序框架

界面

用vs2013新建一个基于对话框的MFC应用程序
添加多个picture控件、编辑框控件、按钮,一个下拉框控件、一个teechart控件
在这里插入图片描述

打开原图按钮

(按钮添加使用见此处:VS2013 简单MFC应用以及teechart使用方法
整个函数目的是获取当前选择的图片完整路径,然后准备好新文件的完整路径
在按钮的处理函数里面添加代码:

void Cgraylevel_correction_LXDlg::OnBnClickedButton1()
{
	// TODO:  在此添加控件通知处理程序代码
	int a = 10,i,j;
	char *cc;
	CString str;
	CString filter;
	filter = "所有文件(*.bmp,*.jpg,*.gif)|*.bmp;*.jpg| BMP(*.bmp)|*.bmp| JPG(*.jpg)|*.jpg||";
	CFileDialog dlg(TRUE, NULL, NULL, OFN_HIDEREADONLY, filter, NULL);
	//直方图数组清零
	for (i = 0; i < h1; i++)
	{
		gramf1[i] = 0;
	}
	for (i = 0; i < h2; i++)
	{
		gramf2[i] = 0;
	}

	//弹框提示
	MessageBox(_TEXT("请选择图片1"));
	if (dlg.DoModal() == IDOK)
	{
		bmpfilepath1 = dlg.GetPathName();     //获取文件路径名   如D:\pic\abc.bmp  
	}
	if (pFileName1a!=NULL)//先检查是否是第二次选择文件,是的话先重新初始化变量,防止文件名追加
	{
		file1fa = _TEXT("file1fa.bmp");	file1fb = _TEXT("file1fb.bmp");//处理后图像保存的文件名
		file1fc = _TEXT("file1fc.bmp"); file1fd = _TEXT("file1fd.bmp");
		file2fa = _TEXT("file2fa.bmp");	file2fb = _TEXT("file2fb.bmp");
		file2fc = _TEXT("file2fc.bmp"); file2fd = _TEXT("file2fd.bmp");
		free(pFileName1a); free(pFileName1b);
		free(pFileName1c); free(pFileName1d);
		free(pFileName2a); free(pFileName2b);
		free(pFileName2c); free(pFileName2d);
	}
	//CString转char
	CStringtochar(bmpfilepath1, &pFileName1);//记得要free(pFileName)
	cc = strstr(pFileName1, ".bmp");//判断是否为bmp文件
	if (cc == NULL){
		str.Format(_T("%s不是bmp文件"), pFileName1);
		MessageBox(str);
		return;
	}
	/*文件路径保存
	pFileName1是通过选择原图获取的路径如“D:\\csdn\\gray-level_correction_noel202007\\test_picture\\lena.bmp”
	newbmppath函数的作用是将pFileName1的文件名去掉变成路径D:\\csdn\\gray-level_correction_noel202007\\test_picture\\
	然后再加上比如"file1fa.bmp"变成新文件的完整路径D:\\csdn\\gray-level_correction_noel202007\\test_picture\\file1fa.bmp
	最后将新文件名放入两个变量file1fa与pFileName1a
	file1fa是CString类型的,用于在showbmp函数里面加载图片
	pFileName1a是char类型的,用于c语言对文件的读写*/
	newbmppath(pFileName1, &file1fa, &pFileName1a);
	newbmppath(pFileName1, &file1fb, &pFileName1b);
	newbmppath(pFileName1, &file1fc, &pFileName1c);
	newbmppath(pFileName1, &file1fd, &pFileName1d);
	MessageBox(_TEXT("请选择图片2"));
	if (dlg.DoModal() == IDOK)
	{
		bmpfilepath2 = dlg.GetPathName();     //获取文件路径名   如D:\pic\abc.bmp  
	}
	CStringtochar(bmpfilepath2, &pFileName2);//记得要free(pFileName)
	cc = strstr(pFileName2, ".bmp");
	if (cc == NULL){
		str.Format(_T("%s不是bmp文件"), pFileName2);
		MessageBox(str);
		return;
	}
	newbmppath(pFileName2, &file2fa, &pFileName2a);
	newbmppath(pFileName2, &file2fb, &pFileName2b);
	newbmppath(pFileName2, &file2fc, &pFileName2c);
	newbmppath(pFileName2, &file2fd, &pFileName2d);
	Cgraylevel_correction_LXDlg::showbmp(bmpfilepath1, 1);//显示原图1
	Cgraylevel_correction_LXDlg::showbmp(bmpfilepath2, 2);//显示原图2
	get_image_size(pFileName1, &h1, &w1);//获取图像尺寸
	in_array1 = allocate_image_array(h1, w1);//根据图像尺寸分配内存
	read_bmp_image(pFileName1, in_array1);//读像素进数组in_array1
	get_image_size(pFileName2, &h2, &w2);
	in_array2 = allocate_image_array(h2, w2);
	read_bmp_image(pFileName2, in_array2);
	for (i = 0; i < h1; i++)
	{
		for (j = 0; j < w1; j++)
		{
			gramf1[in_array1[i][j]]++;//直方图数组
		}
	}
	for (i = 0; i < h2; i++)
	{
		for (j = 0; j < w2; j++)
		{
			gramf2[in_array2[i][j]]++;
		}
	}
}

编辑框控件的使用

编辑框控件用于输入阈值更新至变量以做灰度变换
添加一个编辑框控件,方便自己记忆和编程改下ID
在这里插入图片描述
在这里插入图片描述
添加编辑框控件后,右键添加变量
在这里插入图片描述
类别选value,类型选int,变量名自己起一个,我这里已经添加好了,所以是灰色。其他编辑框同理
在这里插入图片描述
添加好以后可以看到xxxDlg.h里面已经为我们定义好了int型的变量
在这里插入图片描述
在xxxDlg.cpp的DoDataExchange函数里面为我们做了变量m_p1dvalueA与控件ID IDC_P1AVALUEA的绑定
在这里插入图片描述
所以在编辑框里面输入数字,就能通过

UpdateData(true);

来更新输入值到变量使用了。
另外,可以在OnInitDialog()函数里面设定默认值
在这里插入图片描述

picture控件的使用

添加控件,改ID
在这里插入图片描述
在这里插入图片描述
在showbmp函数里面将图片显示至对应控件ID,传入的形参CString filepath是文件要显示的图片路径(这时候已经生成了对应的灰度变换图了并保存至文件中了),int ID形参ID是对应是10个picture控件,自己给他们编的号。不要和前面讲的控件ID名字混了

void Cgraylevel_correction_LXDlg::showbmp(CString filepath, int ID)
{
	CWnd* m_pWnd=NULL;
	switch (ID)
	{
	case 1:m_pWnd = this->GetDlgItem(IDC_PICTURE_LX1); break;//  IDC_PICTURE_LX1为Picture控件ID
	case 2:m_pWnd = this->GetDlgItem(IDC_PICTURE_LX2); break;
	case 3:m_pWnd = this->GetDlgItem(IDC_PIC1_F1); break;
	case 4:m_pWnd = this->GetDlgItem(IDC_PIC1_F2); break;
	case 5:m_pWnd = this->GetDlgItem(IDC_PIC1_F3); break;
	case 6:m_pWnd = this->GetDlgItem(IDC_PIC1_F4); break;
	case 7:m_pWnd = this->GetDlgItem(IDC_PIC2_F1); break;
	case 8:m_pWnd = this->GetDlgItem(IDC_PIC2_F2); break;
	case 9:m_pWnd = this->GetDlgItem(IDC_PIC2_F3); break;
	case 10:m_pWnd = this->GetDlgItem(IDC_PIC2_F4); break;
		default:	; break;
	}
	CRect rect;
	CImage image;
	image.Load(filepath);
	m_pWnd->GetWindowRect(&rect);//将客户区选中到控件表示的矩形区域内  
	CWnd *pWnd1 = NULL;
	pWnd1 = m_pWnd;//获取控件句柄  
	pWnd1->GetClientRect(&rect);//获取句柄指向控件区域的大小  
	CDC *pDc = NULL;
	pDc = pWnd1->GetDC();//获取picture的DC  
	pDc->SetStretchBltMode(STRETCH_HALFTONE);
	image.Draw(pDc->m_hDC, rect);//
	ReleaseDC(pDc);
}

灰度修正按钮

按钮的使用看另一篇博客:VS2013 简单MFC应用以及teechart使用方法
以“灰度修正1(a)”为例,目的是把加载的原图1按下图进行灰度变换。就是将A,B区间里面的灰度值扩展到Z1和Zk,其余区间灰度值不变。A、B、Z1、Z2对应变量m_p1avalueA、m_p1avalueB、m_p1avalueZ1、m_p1avalueZ2
在这里插入图片描述

void Cgraylevel_correction_LXDlg::OnBnClickedCorrection1a()
{
	// TODO:  在此添加控件通知处理程序代码
	if (bmpfilepath1 == "" || bmpfilepath2 == "")
	{
		MessageBox(_T("请先加载原图"));
		return;
	}
	int i, j, a=0,b=0,c=0;
	UpdateData(true);//更新编辑框值到变量
	if (m_p1avalueA == m_p1avalueB)
	{
		MessageBox(_T("A、B不能相等"));
		return;
	}
	//直方图数组清零
	for (i = 0; i < h1; i++)
	{
		gramf1a[i] = 0;
	}
	((CSeries)m_Chart.Series(0)).Clear();
	((CSeries)m_Chart.Series(1)).Clear();

	out_array = allocate_image_array(h1, w1);
	bmheader.height = h1;
	bmheader.width = w1;
	create_allocate_bmp_file(pFileName1a, &bmp_file_header, &bmheader);
	//灰度变换核心代码
	//for循环逐像素读取并进行灰度修正
	// in_array1是原图的像素值数组,out_array是灰度修正后存放像素值的数组
	for (i = 0; i < h1; i++){//h1*w1是图片尺寸
		for (j = 0; j < w1; j++){			
			if ((in_array1[i][j] >= m_p1avalueA) && (in_array1[i][j] <= m_p1avalueB))
			{//如果灰度值在(A,B)区间,则将区间扩展至(Z1,Z2)
				out_array[i][j] = (in_array1[i][j] - m_p1avalueA)*(m_p1avalueZ2 - m_p1avalueZ1) / (m_p1avalueB - m_p1avalueA) + m_p1avalueZ1;
			}//如果不在,则不变
			if ((0 <= in_array1[i][j] && in_array1[i][j] < m_p1avalueA) || (m_p1avalueB < in_array1[i][j] && in_array1[i][j] < 255))
			{
				out_array[i][j] = in_array1[i][j];
				//out_array[i][j] = 0;
			}
		}
	}
	for (i = 0; i < h1; i++)
	{
		for (j = 0; j < w1; j++)
		{
			gramf1a[out_array[i][j]]++;//灰度值统计,用于灰度直方图
		}
	}
	write_bmp_image(pFileName1a, out_array);
	free_image_array(out_array, h1);
	Cgraylevel_correction_LXDlg::showbmp(file1fa, 3);//显示灰度变换图1

//显示灰度变换折线
	((CSeries)m_Chart.Series(0)).AddXY(m_p1avalueA, m_p1avalueZ1, NULL, 0);//A为横坐标 Z1为纵坐标画连线
	((CSeries)m_Chart.Series(0)).AddXY(m_p1avalueB, m_p1avalueZ2, NULL, 0);//B为横坐标 Z2为纵坐标画连线
}

变换前直方图
在这里插入图片描述
变换关系(左下)
在这里插入图片描述
变换后直方图(左下)
在这里插入图片描述
由原图直方图可知,图像大部分灰度集中在50-215,所以将此范围的灰度扩展到了20-255增加了图像的对比度。结果的灰度直方图可以看到灰度已经扩展到了255,符合预期。

组合框框控件选择灰度直方图

添加控件
在这里插入图片描述
右键添加变量
在这里插入图片描述

在在初始化函数OnInitDialog()里面初始化下拉列表
在这里插入图片描述
在控件属性的控件事件里面添加处理函数
在这里插入图片描述
处理代码

void Cgraylevel_correction_LXDlg::OnCbnSelchangeCombo1()
{
	// TODO:  在此添加控件通知处理程序代码
	((CSeries)m_Chart.Series(0)).Clear();
	((CSeries)m_Chart.Series(1)).Clear();
	switch (m_comboxdraw.GetCurSel()+1)
	{
	//调用直方图显示showhistogram函数
	case 1:Cgraylevel_correction_LXDlg::showhistogram(gramf1); break;
	case 2:Cgraylevel_correction_LXDlg::showhistogram(gramf1a); break;
	case 3:Cgraylevel_correction_LXDlg::showhistogram(gramf1b); break;
	case 4:Cgraylevel_correction_LXDlg::showhistogram(gramf1c); break;
	case 5:Cgraylevel_correction_LXDlg::showhistogram(gramf1d); break;
	case 6:Cgraylevel_correction_LXDlg::showhistogram(gramf2); break;
	case 7:Cgraylevel_correction_LXDlg::showhistogram(gramf2a); break;
	case 8:Cgraylevel_correction_LXDlg::showhistogram(gramf2b); break;
	case 9:Cgraylevel_correction_LXDlg::showhistogram(gramf2c); break;
	case 10:Cgraylevel_correction_LXDlg::showhistogram(gramf2d); break;
	default:; break;
	}

}

直方图显示

直方图的数组已经在前面处理部分准备好了
teechart的使用见VS2013 简单MFC应用以及teechart使用方法

void Cgraylevel_correction_LXDlg::showhistogram(int *histogram)//输入直方图数组
{
	int i = 0, j = 0;

	for (i = 0; i < 255; i++)
	{
		//teechart绘直方图
		((CSeries)m_Chart.Series(1)).AddXY(i, histogram[i], NULL, 0);//A为横坐标 Z1为纵坐标画连线
	}
}

处理核心代码

变换a

在这里插入图片描述

	//for循环逐像素读取并进行灰度修正
	// in_array1是原图的像素值数组,out_array是灰度修正后存放像素值的数组
	for (i = 0; i < h1; i++){//h1*w1是图片尺寸
		for (j = 0; j < w1; j++){			
			if ((in_array1[i][j] >= m_p1avalueA) && (in_array1[i][j] <= m_p1avalueB))
			{//如果灰度值在(A,B)区间,则将区间扩展至(Z1,Z2)
				out_array[i][j] = (in_array1[i][j] - m_p1avalueA)*(m_p1avalueZ2 - m_p1avalueZ1) / (m_p1avalueB - m_p1avalueA) + m_p1avalueZ1;
				if (out_array[i][j] ==255)
				{
					a++;
				}
			}//如果不在,则不变
			if ((0 <= in_array1[i][j] && in_array1[i][j] < m_p1avalueA) || (m_p1avalueB < in_array1[i][j] && in_array1[i][j] < 255))
			{
				out_array[i][j] = in_array1[i][j];
				//out_array[i][j] = 0;
			}

		}

	}

变换b

在这里插入图片描述

	//for循环逐像素读取并进行灰度修正
	// in_array1是原图的像素值数组,out_array是灰度修正后存放像素值的数组
	for (i = 0; i < h1; i++){
		for (j = 0; j < w1; j++){
			//如果灰度值在(A,B)区间,则将区间扩展至(Z1,Z2)
			if ((in_array1[i][j] >= m_p1bvalueA) && (in_array1[i][j] <= m_p1bvalueB))
			{
				out_array[i][j] = (in_array1[i][j] - m_p1bvalueA)*(m_p1bvalueZ2 - m_p1bvalueZ1) / (m_p1bvalueB - m_p1bvalueA) + m_p1bvalueZ1;
			}
			//(0, A)区间的灰度置为Z1
			if (in_array1[i][j] < m_p1bvalueA)
			{
				out_array[i][j] = m_p1bvalueZ1;
				//out_array[i][j] = 0;
			}
			//(B, 255)区间的灰度置为Z2
			if ((in_array1[i][j]>m_p1bvalueB ) && (in_array1[i][j] <= 255))
			{
				out_array[i][j] = m_p1bvalueZ2;
				//out_array[i][j] = 0;
			}			
		}
	}

变换c

在这里插入图片描述

	//for循环逐像素读取并进行灰度修正
	// in_array1是原图的像素值数组,out_array是灰度修正后存放像素值的数组
	for (i = 0; i < h1; i++){
		for (j = 0; j < w1; j++){
			//如果灰度值在(A,B)区间,则将区间扩展至(Z1,Z2)
			if ((in_array1[i][j] >= m_p1cvalueA) && (in_array1[i][j] <= m_p1cvalueB))
			{
				out_array[i][j] = (in_array1[i][j] - m_p1cvalueA)*(m_p1cvalueZ2 - m_p1cvalueZ1) / (m_p1cvalueB - m_p1cvalueA) + m_p1cvalueZ1;
				if (out_array[i][j] == 255)
				{
					a++;
				}
			}
			//(0,A)区间的灰度压缩至(0,Z1)
			if ((in_array1[i][j] < m_p1cvalueA) && (in_array1[i][j] >= 0))
			{
				out_array[i][j] = (m_p1cvalueZ1 / m_p1cvalueA)*in_array1[i][j];
			}
			//(B,255)区间的灰度压缩至(Z2,255)
			if ((in_array1[i][j] > m_p1cvalueB) && (in_array1[i][j] <= 255))
			{
				out_array[i][j] = ((255 - m_p1cvalueZ2) / (255 - m_p1cvalueB))*(in_array1[i][j] - m_p1cvalueB) + m_p1cvalueZ2;
			}
		}
	}

变换d

在这里插入图片描述

	//for循环逐像素读取并进行灰度修正
	// in_array1是原图的像素值数组,out_array是灰度修正后存放像素值的数组
	for (i = 0; i < h1; i++){
		for (j = 0; j < w1; j++){
			//将灰度区间在(0, A)上的灰度值扩展到(0, 255)区间
			if ((in_array1[i][j] >= 0) && (in_array1[i][j] < m_p1dvalueA))
			{
				out_array[i][j] = (255 / m_p1dvalueA)*in_array1[i][j];
				//out_array[i][j] = 0;
			}
			//(A,B)区间的灰度扩展至(0,255)
			if ((in_array1[i][j] >= m_p1dvalueA) && (in_array1[i][j] < m_p1dvalueB))
			{
				out_array[i][j] = (255 / (m_p1dvalueB - m_p1dvalueA))*(in_array1[i][j] - m_p1dvalueA);
				//out_array[i][j] = in_array1[i][j];
			}
			//(B, 255)区间的灰度扩展至(0, 255)
			if ((in_array1[i][j] >= m_p1dvalueB) && (in_array1[i][j] <= 255))
			{
				out_array[i][j] = (255 / (255 - m_p1dvalueB))*(in_array1[i][j] - m_p1dvalueB);
				//out_array[i][j] = 0;
			}

		}

	}

学习笔记,如有错漏,敬请指正
--------------------------------------------------------------------------------------------诺有缸的高飞鸟202007

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

诺有缸的高飞鸟

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值