《多媒体信息处理》课程设计——基于MFC的数字图像处理初步

特别说明: 本报告中的图像处理函数有宏定义和变量定义,参考注释说明。

#define WIDTHBYTES(bits) ((bits+31)/32*4)
#define Cnumber(x,y,z) *(m_pDIBData+(x)*GetBmpRealWidth()+(y)*3+(z))
//用Cnumber(x,y,z)代表从下到上,从左到右的第x行,第y列,第z个颜色分量。

#define Cnumber1(x,y,z) *(n_pDIBData+(x)*GetBmpRealWidth()+(y)*3+(z))//用于备份真彩图 
#define gray(x,y) *(m_pDIBData+(x)*GetBmpRealWidth()+y) 
#define gray1(x,y) *(n_pDIBData+(x)*GetBmpRealWidth()+y )//用于备份灰度图 

在此作说明,程序可以在Visual C++6.0,Win10系统下运行。

本次多媒体信息处理课程设计主要涉及MFC编程数字图像处理(位图) 的应用,根据课程设计要求完成了基于MFC的数字图像处理,包括几何变换(镜像、选装、缩放、伽马校正)、颜色变换(转灰度、亮度、对比度)、还原等操作。本软件还可以继续完善,有相当多的功能可以继续添加,比如滤波操作、边缘化等。在此博客中,我将从位图的文件编码格式讲起,然后分别对图像的各种操作介绍,按照要求->原理->程序实现->效果展实的逻辑顺序展开。


对于想要获取此课程设计报告word/PDF排版软件开发包(包括项目开发工程、二进制解码器Binary Viewer、位图测试图片) 的同学,欢迎光顾小生寒舍 GitHub: https://github.com/ChromeWei?tab=repositories 也可点击我的下载进行下载,但需要积分。

直接获取方式:关注微信公众号迈微电子研发社,回复“多媒体课程设计”,程序开发源代码。

特此感谢, 课程设计过程中任课老师邓老师和好友沈同学给予的指导和帮助!

摘要
  多媒体技术涉及面非常广,是一项实用性很强的技术。只有进行实际操作,将理论应用于实际中,才能确实掌握书中的知识点。通过使用VC++6.0,设计开发多媒体软件,学习掌握对多媒体数据特别是数字图像处理的方法实现,对图像进行反色变换、镜像、对比度和亮度调节、缩放与还原、提取彩色图像的RGB分量等操作。由于在人工智能学术冬令营的培训课上学到了相关的图像处理算法,可以很好的应用至此。经实际测试,开发的软件满足课程设计的要求,并在此基础上对软件的用户交互界面上得到优化。
  本设计报告将按照课程设计概述、数字图像处理基础理论介绍、图像处理具体方法及实现、课设总结进行编写,其中第三部分图像处理具体方法及实现部分是本报告中的重要部分。

关键字: MFC;数字图像处理;镜像;反色;缩放



第一部分 课程设计概述

1.1 课程设计的目的与任务

  多媒体技术涉及面非常广,是一项实用性很强的技术。只有进行实际操作,将理论应用于实际中,才能确实掌握书中的知识点。通过使用VC++,设计开发多媒体软件,学习掌握对多媒体数据特别是数字图像处理的方法实现,巩固学习成果;并在大三“面向对象程序设计”的基础上进一步熟练面向对象编程和windows编程的方法。

1.2 课程设计题目

  基于MFC的图像处理初步

1.3 设计功能要求

(1)显示个人信息;(2)反色变换;(3)图像亮度调节;(4)图像对比度调节;(5)提取彩色图像的RGB分量;(6)镜像操作;(7)γ校正;(8)旋转90(270)度;(9)24位真彩色图转8位灰度图;(10)缩放操作设置还原功能;(11)设置还原功能;

1.4 课程设计的内容与要求

(1)软件调试;
(2)源程序清单与注释(重要,特别是函数功能需要文字介绍);
(3)程序运行结果截图;
(4)写课程设计报告;

1.5 实验仪器设备及器件

(1)PC机;
(2)操作系统:win7以上;
(3)软件环境:VC++6.0、MFC框架;


第二部分 数字图像处理基础理论

  数字图像处理是指将图像信号转换成数字信号并利用计算机对其进行处理的过程。图像处理中,输入的是质量低的图像,输出的是改善质量后的图像,常用的图像处理方法有图像增强、复原、编码、压缩等。

2.1 图像的基本属性

2.1.1图像的数字化

  图像数字化过程就是对连续图像f(x,y)进行空间和幅度离散化的过程。采样过程是从X,Y方向分别采样,并且满足采样定理;量化是对灰度或者颜色样本的离散化,黑白灰度图像,彩色(与颜色空间相关)。

2.1.2图像数字化设备

  图像数字化设备主要包含光源、光传感器(光电转换)和扫描系统,而扫描系统又包#含扫描仪和CCD(电荷耦合器件)阵列。

2.1.3图像的RGB颜色模型

  显示器通过红、绿和蓝荧光粉发射光线产生彩色。彩色图像的颜色需要RGB或它们的颜色空间变换结果(三个数值)来表示。
  RGB颜色模型应该是我们在平时生活中接触最多的一种颜色模型,也就是我们通常说的红、绿、蓝三原色模型。
  RGB颜色模型是将红、绿、蓝3种不同颜色,根据亮度配比的不同进行混合,从而表现出不同的颜色。由于在实现上使用了3种颜色的定量配比,因此该模型也被称为加色混色模型。通过3种最基本颜色的混合叠加来表现出任意的一种颜色的方法,特别适用于显示器等主动发光的显示设备。

图2.1 RGB相加混色模型

2.1.4图像的种类

  图像主要分为矢量图(绘制)和点位图(采集)。
  矢量图形,是由叫作矢量的数学对象所定义的直线和曲线组成的。矢量根据图形的几何特性来对其进行描述,矢量图形与分辨率无关。
  位图图象,也叫作栅格图象。位图图象是用小方形网格(位图或栅格),即人所共知的象素来代表图象,每个象素都被分配一个特定位置和颜色值。

2.2 BMP文件格式

2.2.1文件结构

  位图文件(Bitmap-File,BMP)格式是Windows采用的图像文件存储格式,由4个部分组成:位图文件头(bitmap-file header)、位图信息头(bitmap-information header)、彩色表(color table)和定义位图的字节阵列。

表2.1 文件结构
位图文件的组成 结构名称 符号
位图文件头 (bitmap-file header) BITMAPFILEHEADER
位图信息头 (bitmap-information header) BITMAPINFOHEADER
彩色表(color table) RGBQUAD aColors[]
图像数据阵列字节 BYTE aBitmapBits[]

2.2.2位图文件头

  位图文件头包含文件类型、文件大小、存放位置等信息。结构定义如下:

  typedef struct tagBITMAPFILEHEADER
  {
  	UNIT	bfType; //说明文件类型,在windows系统中为BM。
  	DWORD	bfSize;// 说明文件大小。
  	UINT	bfReserved1;// 保留,设置为0。
  	UINT	bfReserved2;// 保留,设置为0。
  	DWORD	bfOffBits;// 说明实际图形数据的偏移量
  }BITMAPFILEHEADER;

2.2.3位图信息头

  位图信息头包含位图的大小、压缩类型、和颜色格式,结构定义如下:

  typedef struct tagBITMAPINFOHEADER
  {
  	DWORD	biSize; //图像大小
  	LONG	biWidth; //图像宽度
  	LONG	biHeight; //图像高度
  	WORD	biPlanes; //为目标设备说明位面数,其值设为1
  	WORD	biBitCount;// 每个像素的位数,单色位图为1,256色为8,24bit为24。
  	DWORD	biCompression;// 压缩说明,BI_RGB:无压缩,BI_RLE8:8位RLE压缩,
						//BI_RLE4:4位RLE压缩,一般无压缩。
  	DWORD	biSizeImage;// 说明图像大小,如无压缩,可设为0
  	LONG	biXPelsPerMerer;// 水平分辨率
  	LONG	biYPelsPerMerer;// 垂直分辨率
  	DWORD	biClrUsed;// 位图使用的颜色数
  	DWORD	biClrImportant;// 重要颜色数目
  }BITMAPINFOHEADER;

2.2.4彩色表

  彩色表包含的元素与位图所具有的颜色数目相同,像素颜色用结构RGBQUAD来表示:

typedef struct tagRGBQUAD
{
	BYTE	rgbBlue; //指定蓝色强度
	BYTE	rgbGreen;// 指定绿色强度
	BYTE	rgbRed;// 指定红色强度
	BYTE	rgbReserved;// 保留,设为0
}RGBQUAD;

2.2.5位图数据

  紧跟在彩色表后的是图像数据(或索引号)阵列,图像每一扫描行有连续的字节组成,扫描行由底向上存储,阵列中第一字节为左下角像素,最后一字节为右上角像素。
  24位位图没有彩色表,一个像素占三个字节,从左到右依次储存蓝、绿、红的颜色值。8位位图一个像素占一个字节,有彩色表,像素值是彩色表中的索引号,但表中每一项的RGB值相等,从0-255共256项,对应的索引号也是0-255,所以可以将索引号看成灰度(亮度)值直接做各种调整。

2.3 JPEG文件压缩算法

2.3.1 JPEG文件格式

  JPEG(Joint Photogaphic Experts Group,联合图像专家组)由ISO和IEC(International Electrotechnical Commission,国际电工委员会)两个机构的专家组成。提出 “连续色调静止图像的数字压缩和编码(Digital Compression and Coding of Continuous-tone Still Images)”标准。
  它是彩色、灰度、静止图像的第一个国际标准。每个分量、每个像素的电平规定为255级,用8bit表示。
  JPEG算法的核心是基于离散余弦变换(DCT)的编码方式。
  JPEG采用ITU-R BT.601标准定义的彩色空间转换:

    Y = 0.299R + 0.587G + 0.114B
    Cr = (0.500R - 0.4187G - 0.0813B) + 128
    Cb = (-0.1687R - 0.3313G + 0.500B) + 128

2.3.2 编解码的基本过程

在这里插入图片描述

图2.2 JPEG编解码的基本过程
  1. 分块并作DCT变换
    首先分离亮度和色度信号,再各分成8*8的块,对每一块做二维离散余弦变换。
  2. 量化
    DCT的结果还是8*8的矩阵,左上代表低频分量,右下代表高频分量,对每块的64个数据用下面的量化表量化。
表2.2 亮度量化表和色度量化表

在这里插入图片描述

  1. Z字形编排:
    对量化结果按下图顺序编排,左上第一个数据代表直流分量(DC系数),其他代表交流分量(AC系数)。
  2. 对AC系数采用RLE编码
  3. 对每块的DC系数进行DPCM(差分脉冲编码调制)
  4. 最后进行霍夫曼编码

2.4 其他常用图像格式

  1. PSD(Photoshop Document)
    著名的Adobe公司的图像处理软件Photoshop的专用格式。
  2. PNG(Portable Network Graphics)
    PNG 是一种新兴的网络图像格式。在1994年底,由于Unysis公司宣布GIF拥有专利的压缩方法,要求开发GIF软件的作者须缴费,促使免费的png图像格式的诞生。PNG结合GIF及JPG两家之长,于1996年10月1日向国际网络联盟提出并得到推荐认可标准。大部分绘图软件和浏览器开始支持PNG图像浏览。
  3. SWF(Shockwave Format)
    Flash动画格式。可以边下载边看,因此特别适合网络传输。目前已成为网上动画的事实标准。


第三部分 图像处理具体方法及实现

  此部分是本报告的核心部分,将会详细说明各个设计要求的具体实现,包括实现原理、实现方法、程序编写、实现效果对比等,满足每个设计的所有要求,并在用户交互界面上做了美化。

3.1 显示个人信息

要求: 在程序中建立菜单,点击出现自己的个人信息(姓名学号等)。
原理: 使用AfxMessageBox()函数。
程序实现:

void CPictureView::OnAppAuthor() 
{
	// TODO: Add your command handler code here
	AfxMessageBox(" 姓名:张伟\n 学号:160901137 \n\n 春节快乐!!!", MB_OK | MB_ICONINFORMATION);   
    
    //用法参考链接:https://blog.csdn.net/qq1041256623/article/details/17321853
}

实现效果:
在这里插入图片描述

图3.1 个人信息显示效果图

3.2 反色变换

原理:
  对于灰度值或彩色值x,取值范围0-255,则255-x为x的反色,所谓反色变换就是数字图像每一个像素的灰度值或彩色值都变为其反色。
要求:
  在菜单中设置一个反色操作按钮,点击该按钮对当前视图中的图像做反色变换并显示结果。可以对灰度和彩色图操作。
程序实现:

BOOL DIB::Fanse()//反色变换 
{
	int x,y; 
	BYTE i; 
	if(bih.biBitCount==24)
	{ 
		for(x=0;x<GetDIBHeight();x++) 
		{ 
			for(y=0;y<GetDIBWidth();y++) 
			{ 
				//像素的彩色值变为其反色
				Cnumber(x,y,0)=255-Cnumber(x,y,0); 
				Cnumber(x,y,1)=255-Cnumber(x,y,1); 
				Cnumber(x,y,2)=255-Cnumber(x,y,2);

				if(Cnumber(x,y,0)>255) Cnumber(x,y,0)=255; 
				else if(Cnumber(x,y,0)<0) Cnumber(x,y,0)=0; 
				if(Cnumber(x,y,1)>255) Cnumber(x,y,1)=255; 
				else if(Cnumber(x,y,1)<0) Cnumber(x,y,1)=0; 
				if(Cnumber(x,y,2)>255) Cnumber(x,y,2)=255; 
				else if(Cnumber(x,y,2)<0) Cnumber(x,y,2)=0; 
			} 
		} 
		return true; 
	}
	
	else if(bih.biBitCount==8) 
	{ 
		for(x=0;x<GetDIBHeight();x++) 
		{ 
			for(y=0;y<GetDIBWidth();y++) 
			{ 
				gray(x,y)=255-gray(x,y);//像素的彩色值变为其反色 
				i = gray(x,y); 
				if(i>255) i=255; 
				if(i<0) i=0; 
			}
		} 
		return true;
	}
	
		else 
		{ 
			AfxMessageBox("原图不是真彩图或灰度图"); 
			return false;
		} 
}

实现效果:

在这里插入图片描述 在这里插入图片描述
       反色前             反色后

图3.2-2 反色变换效果图

3.3 图像亮度调节

原理:
  对图像像素的灰度值或彩色值乘一个系数就可以加大(系数大于1)和降低(系数小于1)亮度。注意像素值最大值是255。
要求:
  在菜单中设置两个调节亮度按钮,分别用于加大亮度和减小亮度。加大和减小的幅度自己设定。调节结果即时在视图中显示。可以对灰度和彩色图操作。
程序实现:

BOOL DIB::BrightnesINC() //增大亮度
{
	int x,y; 
	double r,g,b;
	double i; 

	if(bih.biBitCount==24)
	{ 
		for(x=0;x<GetDIBHeight();x++) 
		{ 
			for(y=0;y<GetDIBWidth();y++) 
			{ 
				r=Cnumber(x,y,2)*1.5; g=Cnumber(x,y,1)*1.5; b=Cnumber(x,y,0)*1.5;

				if(r>255) r=255; else if(r<0) r=0;
				if(g>255) g=255; else if(g<0) g=0;
				if(b>255) b=255; else if(b<0) b=0;
				
				Cnumber(x,y,2)=(BYTE)r; Cnumber(x,y,1)=(BYTE)g; Cnumber(x,y,0)=(BYTE)b; 
			} 
		} 
		return true; 
	}

	
	else if(bih.biBitCount==8) 
	{ 
		for(x=0;x<GetDIBHeight();x++) 
		{ 
			for(y=0;y<GetDIBWidth();y++) 
			{ 
				i=gray(x,y)*1.5; 
				if (i>255)	i=255; 
				if (i<0)	i=0; 
				gray(x,y)= (BYTE)i;
			}
		}
		return true;
	} 
	
	else
	{
		AfxMessageBox("原图不是真彩或灰度"); 
		return false; 
	} 
}

实现效果:

在这里插入图片描述

图3.3-1 亮度、对比度调节界面图

在这里插入图片描述在这里插入图片描述
        亮度增大                 亮度减小

图3.3-2 亮度调节效果图

3.4 图像对比度调节

原理:
  对图像像素的灰度值或彩色值做如下操作即为调大对比度:对大于某个阈值的像素值使其更大,对小于这个阈值的像素值使其更小。反之则为调小对比度。阈值可以是127.5(这是对比度调节最粗糙的算法,可以在网上查找更好的方法改进)。同样,当加大数值后超过255,就定为255;当减小数值后小于0,就定为0。
要求:
  在菜单中设置两个调节对比度按钮,分别用于加大对比度和减小对比度。加大和减小的幅度自己设定。调节结果即时在视图中显示。可以对灰度和彩色图操作。
程序实现:

BOOL DIB::ContrastINC() //增大对比度
{
	int x,y;
	double r,g,b;
	double i; 

	if(bih.biBitCount==24)
	{ 
		for(x=0;x<GetDIBHeight();x++) 
		{ 
			for(y=0;y<GetDIBWidth();y++) 
			{ 
				r=Cnumber(x,y,2); g=Cnumber(x,y,1); b=Cnumber(x,y,0); 
				
				if(r>127.5) r=r+20;//大于阈值,使其更大 
				else if(r<127.5) r=r-10;//小于阈值,使其更小
				if(r>255) 	r=255; else if(r<0) r	=0; 
				if(g>127.5)	 g=g+20; else if(g<127.5) 	g=g-10; 
				if(g>255) g=255; else if(g<0) g=0; 
				if(b>127.5) b=b+20; else if(b<127.5) b=b-10; 
				if(b>255) b=255; else if(b<0) b=0; 
				
				Cnumber(x,y,2)=(BYTE)r; Cnumber(x,y,1)=(BYTE)g; Cnumber(x,y,0)=(BYTE)b; 
			} 
		} 
		return true; 
	}
	else if(bih.biBitCount==8) 
	{ 
		for(x=0;x<GetDIBHeight();x++) 
		{ 
			for(y=0;y<GetDIBWidth();y++) 
			{  
				i=gray(x,y); 
				if (i>127.5) i=i+10; else if (i<127.5) i=i-10; 
				if(i>255)i=255; else if(i<0)i=0; 

				gray(x,y)=(BYTE)i; 
			} 
		} 
		return true;
	} 

	else 
	{ 
		for(x=0;x<GetDIBHeight();x++)
			AfxMessageBox("原图不是真彩图或灰度"); 
		return false; 
	} 
}

实现效果:
在这里插入图片描述

图3.4 对比度调节效果图

3.5 提取RGB分量

原理:
  该操作只针对彩色图像,以提取红色分量为例,将蓝色和绿色分量的值变为0,显示出来的就是红色分量的图像。蓝色和绿色与之类似。
要求:
  在菜单中设置三个按钮,分别用于提取红、绿、蓝分量。调节结果即时在视图中显示。同时要能够判断当前图像是否是彩色图,如果不是出现提示信息。
程序实现:
  以提取红色分量为例,将蓝色和绿色分量的值变为0(r=Cnumber(x,y,2); g=0; b=0;//蓝色和绿色分量置0),即可显示红色分量,蓝色和绿色类似。

BOOL DIB::ExtractRED() //提取R分量 
{
	int x,y; 
	double r,g,b; 
	if(bih.biBitCount!=24) 
	{ 
		AfxMessageBox("原图不是真彩图");  
		return false; 
	} 
	
	else 
	{ 
		for(x=0;x<GetDIBHeight();x++) 
		{ 
			for(y=0;y<GetDIBWidth();y++) 
			{ 
				r=Cnumber(x,y,2); g=0; b=0;//蓝色和绿色分量置0 
				if(r>255) r=255; else if(r<0) r=0; 
				
				Cnumber(x,y,2)=(BYTE)r; 
				Cnumber(x,y,1)=(BYTE)g; 
				Cnumber(x,y,0)=(BYTE)b; 
			} 
		} 
	} 
	return true; 
}

实现效果:
该操作只针对彩色图像,若不是彩色会给以信息提示。

在这里插入图片描述

图3.5-1 提取RGB分量界面图

在这里插入图片描述

图3.5-2 提取RGB分量示意图

3.6 镜像操作

原理:
  将图像的像素按镜像重新排列,即为对图像做镜像操作,可以是左右镜像或上下镜像。
要求:
  在菜单中设置镜像操作按钮,对当前图像做镜像操作并即时显示。至少要完成左右镜像的操作。可以对灰度和彩色图操作。
程序实现:

BOOL DIB::MirrorLeft2Right() //图像左右镜像
{
	int x,y; 
	double temp;
	double i; 

	if(bih.biBitCount==24)
	{ 
		for(x=0;x<GetDIBHeight();x++) 
		{ 
			for(y=0;y<=GetDIBWidth()/2;y++) 
			{ 
				if(temp>255) temp=255; else if(temp<0) temp=0; 

				for(int k = 0; k<3; k++)
				{
					//像素按左右镜像重新排列
					temp=Cnumber(x,GetDIBWidth()-y-1,k); 
					Cnumber(x,GetDIBWidth()-y-1,k)=Cnumber(x,y,k);
					Cnumber(x,y,k)=(BYTE)temp;
				}
			} 
		} 
		return true; 
	}

	else if(bih.biBitCount==8) 
	{ 
		for(x=0;x<=int(GetDIBHeight()/2);x++) 
		{ 
			for(y=0;y<GetDIBWidth();y++) 
			{ 
				i=gray(GetDIBHeight()-x-1,y);
				gray(GetDIBHeight()-x-1,y)=(BYTE)gray(x,y); 
				gray(x,y)=(BYTE)i;
			}
		}
		return true;
	} 	

	else { 
		AfxMessageBox("原图不是真彩图或灰度"); 
		return false; 
	} 
}

实现效果:

在这里插入图片描述

图3.6 镜像操作效果图

3.7 γ校正

原理:
  对图像的像素值归一化后取γ次方,再扩大至0-255的范围内。如下式:
y = 255 ( x / 255 ) γ y =255* (x/255)^γ
其中,x代表校正前像素值,y代表校正后像素值。

要求:
  在菜单中设置γ校正操作按钮,点击按钮出现对话框,输入γ值(0.5-1.5之间),确定后对图像γ校正并即时显示。可以对灰度和彩色图操作。

程序实现:

BOOL DIB::GamaCorrection() //图像伽马校正 
{ 
	int x,y; 
	double r,g,b;
	double i; 
	
	mydialog dlg; 
	
	dlg.m_value=NULL; //响应并输入r值

	if(dlg.DoModal()!=IDOK) 
	{ 
		return false; 
	} 
	
	float value=dlg.m_value;//将输入的r值传送给value	
	delete dlg;
	
	if(bih.biBitCount==24) 
	{ 
		for(x=0;x<GetDIBHeight();x++) 
		{ 
			for(y=0;y<GetDIBWidth();y++) 
			{ 
				r=Cnumber(x,y,2); g=Cnumber(x,y,1); b=Cnumber(x,y,0); 

				r=255*pow(r/255,value); 
				if(r>255) r=255; else if(r<0) r=0; 
				g=255*pow(g/255,value); 
				if(g>255) g=255; else if(g<0) g=0; 
				b=255*pow(b/255,value); 
				if(b>255) b=255; else if(b<0) b=0;  
					
				Cnumber(x,y,2)=(BYTE)r; 
				Cnumber(x,y,1)=(BYTE)g; 
				Cnumber(x,y,0)=(BYTE)b; 
			} 
		}
		return true;
	}
	
	else if(bih.biBitCount==8)
	{
		for(x=0;x<GetDIBHeight();x++) 
		{ 
			for(y=0;y<GetDIBWidth();y++) 
			{ 
				i=gray(x,y); 
				i=255*pow(i/255,value); 
				if(i>255) i=255; else if(i<0) i=0;
				gray(x,y)=(BYTE)i;
			}
		}
		return true;
	}
	
		else 
		{
			AfxMessageBox("原图不是真彩图或灰度"); 
			return false; 
		} 
}

实现效果:
在这里插入图片描述

图3.7-1 γ校正和旋转操作界面图

在这里插入图片描述

图3.7-2 γ校正效果图

3.8 旋转90(270)度

原理:
  将图像的像素按旋转度数重新排列,并交换图像信息头中的长度宽度值和垂直水平分辨率数值。
要求:
  在菜单中设置旋转操作按钮,对当前图像做旋转操作并即时显示。可以对灰度和彩色图操作。
程序实现:

BOOL DIB::Rotation90() //图像右旋转90度(只能对正方形图像操作) 
{ 
	int x,y; 
	double r,g,b;
	double i,j; 
	
	if(GetDIBWidth()%GetDIBHeight())
	{
		AfxMessageBox("请给我一张正方形图片:)"); 
		return false; 
	}

	if(bih.biBitCount==24) 
	{ 
		for(x=0;x<GetDIBHeight();x++) 
		{ 
			for(y=x;y<GetDIBWidth();y++) 
			{ 
				r=Cnumber(x,y,2);Cnumber(x,y,2)=Cnumber(y,x,2);Cnumber(y,x,2)=(byte)r; 
				g=Cnumber(x,y,1);Cnumber(x,y,1)=Cnumber(y,x,1);Cnumber(y,x,1)=(byte)g; 
				b=Cnumber(x,y,0);Cnumber(x,y,0)=Cnumber(y,x,0);Cnumber(y,x,0)=(byte)b;
				//将图像各个像素按转置交换(相当于矩阵转置) 
			} 
		} 
		for(x=0;x<GetDIBHeight()/2;x++) 
		{ 
			for(y=0;y<GetDIBWidth();y++) 
			{ 
				r=Cnumber(x,y,2);Cnumber(x,y,2)=Cnumber(GetDIBHeight()-1-x,y,2);Cnumber(GetDIBHeight()-1-x,y,2)= (byte)r;
				g=Cnumber(x,y,1);Cnumber(x,y,1)=Cnumber(GetDIBHeight()-1-x,y,1);Cnumber(GetDIBHeight()-1-x,y,1)= (byte)g; 
				b=Cnumber(x,y,0);Cnumber(x,y,0)=Cnumber(GetDIBHeight()-1-x,y,0);Cnumber(GetDIBHeight()-1-x,y,0)= (byte)b; 
				//将图像像素转置后,再将图像像素值按镜像排列,综合转置与镜像,即可达到图像旋转90
			} 
		} 
		return true; 
	}  

	else if(bih.biBitCount==8) 
	{ 
		for(x=0;x<GetDIBHeight();x++) 
		{ 
			for(y=0;y<GetDIBWidth();y++) 
			{ 	
 
				i=gray(y,x); gray(y,x)=(BYTE)gray(x,y); gray(x,y)=(BYTE)i; 
				if(i>255)	i=255; 
				else if(i<0)	i=0; 
			}
		}
		for(x=0;x<GetDIBHeight()/2;x++) 
		{ 
			for(y=0;y<GetDIBWidth();y++) 
			{ 					
				j=gray(x,y); gray(x,y)=gray(GetDIBHeight()-x-1,y);gray(GetDIBHeight()-x-1,y)=(byte)j;
			}
		}
		return true;
	} 
	

	else { 
		AfxMessageBox("原图不是真彩图或灰度"); 
		return false; 
	} 
}

实现效果:
在这里插入图片描述

图3.8 旋转效果图

3.9 缩放操作

原理:最近邻插值
  不需要计算,在待求象素的四邻象素中,将距离待求象素最近的邻象素灰度赋给待求象素:
在这里插入图片描述

图3.9最近邻插值示意图
  设i+u,j+v(i,j为正整数,u,v为大于零小于1的小数,下同)为待求象素坐标,则待求象素灰度的值 f(i+u,j+v);如果(i+u, j+v)落在A区,即u<0.5, v<0.5,则将左上角象素的灰度值赋给待求象素,同理,落在B区则赋予右上角的象素灰度值,落在C区则赋予左下角象素的灰度值,落在D区则赋予右下角象素的灰度值。
  最邻近算法计算量较小,但可能会造成插值生成的图像灰度上的不连续,在灰度变化的地方可能出现明显的锯齿状。

(参考原文链接:C++——bmp图像缩放(插值)https://blog.csdn.net/wanty_chen/article/details/80283872)

要求:
  设置按钮,实现图像的缩小和放大(可采用默认的缩放倍数,也可以输入缩放倍数)并显示(注意:在实验三的程序中,如果图像过大,自动做了缩小显示,所以测试用图不要过大,或者修改程序,始终按照原始大小显示图像,但那样就有可能需要加入滚动条。),同时可以用保存按钮存盘。

程序实现:

//几个宏定义
#define WIDTHBYTES(bits) ((bits+31)/32*4)
#define Cnumber(x,y,z) *(m_pDIBData+(x)*GetBmpRealWidth()+(y)*3+(z))
//用Cnumber(x,y,z)代表从下到上,从左到右的第x行,第y列,第z个颜色分量。

BOOL DIB::ScalingNearest()  //图像缩放
{	
	mydialog1 dlg;
	float times = 0;
	dlg.m_scaling=NULL;//响应并输入缩放系数
	if (dlg.DoModal() != IDOK)
	{
		return false;	//返回
	}	
	times = dlg.m_scaling;//将输入的缩放系数传送给ExpScalValue	
	delete dlg;

	int width = GetDIBWidth();
	int height = GetDIBHeight();
	int MYDRAW_HEIGHT = height * times;
	int MYDRAW_WIDTH = width * times;
	int T_width = WIDTHBYTES(MYDRAW_WIDTH * bih.biBitCount);		
	int L_width = WIDTHBYTES(width * bih.biBitCount);				
	long T_size = T_width * MYDRAW_HEIGHT;							
	long L_size = L_width * height;									
	BYTE* pColorData = (BYTE*)new char[L_size];						
	memset(pColorData, 0, L_size);									
	BYTE* pColorDataMid = (BYTE*)new char[T_size];					
	memset(pColorDataMid, 0, T_size);								
	memcpy(pColorData, m_pDIBData, L_size);							
	bih.biWidth = m_pBMI->bmiHeader.biWidth = MYDRAW_WIDTH;
	bih.biHeight = m_pBMI->bmiHeader.biHeight = MYDRAW_HEIGHT;
	bih.biSizeImage = m_pBMI->bmiHeader.biSizeImage = T_size;
	bfh.bfSize = T_size + bfh.bfOffBits;
	for (int hnum = 0; hnum < MYDRAW_HEIGHT; hnum++)
		for (int wnum = 0; wnum < MYDRAW_WIDTH; wnum++)
		{
			double d_original_img_hnum = hnum * height / (double)MYDRAW_HEIGHT;
			double d_original_img_wnum = wnum * width / (double)MYDRAW_WIDTH;
			int i_original_img_hnum = (int)d_original_img_hnum;
			int i_original_img_wnum = (int)d_original_img_wnum;
			double distance_to_a_x = d_original_img_wnum - i_original_img_wnum;   
			double distance_to_a_y = d_original_img_hnum - i_original_img_hnum;
			int original_point_a = i_original_img_hnum * L_width + i_original_img_wnum * 3;
			int original_point_b = i_original_img_hnum * L_width + (i_original_img_wnum + 1) * 3;
			int original_point_c = (i_original_img_hnum + 1) * L_width + i_original_img_wnum * 3;
			int original_point_d = (i_original_img_hnum + 1) * L_width + (i_original_img_wnum + 1) * 3;
			if (i_original_img_hnum + 1 >= height - 1)
			{
				original_point_c = original_point_a;
				original_point_d = original_point_b;
			}
			if (i_original_img_wnum + 1 >= width - 1)
			{
				original_point_b = original_point_a;
				original_point_d = original_point_c;
			}
			int pixel_point = hnum * T_width + wnum * 3;
			pColorDataMid[pixel_point] =
				pColorData[original_point_a] * (1 - distance_to_a_x) * (1 - distance_to_a_y) +
				pColorData[original_point_b] * distance_to_a_x * (1 - distance_to_a_y) +
				pColorData[original_point_c] * distance_to_a_y * (1 - distance_to_a_x) +
				pColorData[original_point_c] * distance_to_a_y * distance_to_a_x;
			pColorDataMid[pixel_point + 1] =
				pColorData[original_point_a + 1] * (1 - distance_to_a_x) * (1 - distance_to_a_y) +
				pColorData[original_point_b + 1] * distance_to_a_x * (1 - distance_to_a_y) +
				pColorData[original_point_c + 1] * distance_to_a_y * (1 - distance_to_a_x) +
				pColorData[original_point_c + 1] * distance_to_a_y * distance_to_a_x;
			pColorDataMid[pixel_point + 2] =
				pColorData[original_point_a + 2] * (1 - distance_to_a_x) * (1 - distance_to_a_y) +
				pColorData[original_point_b + 2] * distance_to_a_x * (1 - distance_to_a_y) +
				pColorData[original_point_c + 2] * distance_to_a_y * (1 - distance_to_a_x) +
				pColorData[original_point_c + 2] * distance_to_a_y * distance_to_a_x;
		}
		if (m_pDIBData != NULL)
			delete m_pDIBData;
		m_pDIBData = pColorDataMid;
		delete pColorData;
}

实现效果:
在这里插入图片描述
缩小0.5倍
在这里插入图片描述
原图像
在这里插入图片描述
放大3倍
在这里插入图片描述

图3.10 图像大小信息

3.10 设置还原功能

原理:
  在读取图像的时候就做一个备份,需要时读取。
要求:
  不管对图像做什么操作,点击还原按钮,显示原始图像。
程序实现:
  这里设计两种方法。
  方法一: 将预先保存的原图重新复制给Cnumber(x,y,z)

		Cnumber(x,y,0)=Cnumber1(x,y,0); 
		Cnumber(x,y,1)=Cnumber1(x,y,1);
		Cnumber(x,y,2)=Cnumber1(x,y,2);

  方法二: 重新获取原图像

CString fname;
void CPictureView::OnReset() 
{
	// TODO: Add your command handler code here
	if(m_dib.m_pBMI==NULL||m_dib.m_pDIBData==NULL) 
	{ 
		AfxMessageBox("没有图像资源"); 
		return; 
	} 
	if(!m_dib.LoadFromFile(fname))//载入内存中的原始图片 
		return; 
	Invalidate(TRUE);
}

3.11 24位彩色转8位灰度图

原理:
   灰度值采用亮度方程计算,按照标准8位BMP灰度图格式生成新图像。
要求:
  设置按钮,实现图像转换并显示,同时可以用保存按钮存盘。
程序实现:

BOOL DIB::Convert24toGray() //24位真彩色转8位灰度图
{ 
	int i,j,n; 
	int DataByte; 
	BITMAPINFO* pBMI = NULL;//新定义位图信息头结构指针pBMI 
	BYTE* pDIBData = NULL;//新定义指向位图像素灰度值的指针pDIBData
	
	//判断打开图像是否为 24 位图 
	if(bih.biBitCount!=24) 
	{ 
		AfxMessageBox("原图不是 24 位真彩色图"); 
		return false; 
	} 

	long w=GetDIBWidth();
	if(w%4!=0)//判断宽度是不是4的倍数
	{
		n=w/4; 
		w=4*n+4;
	} 
	
	DataByte=w;//一行带有0的数据
	int supply = DataByte - GetDIBWidth(); //补0的个数
	pBMI=(BITMAPINFO*)new char[sizeof(BITMAPINFOHEADER)+sizeof(RGBQUAD)*256]; //为信息头分配内存空间 
	pDIBData=(BYTE*)new char[GetDIBHeight()*DataByte];//为新图像的像素点分配内存空间(长×宽) 
	
	//建立新的颜色表 
	for(i=0;i<256;i++)
	{ 
		pBMI->bmiColors[i].rgbRed= (unsigned char)i; 
		pBMI->bmiColors[i].rgbGreen=(unsigned char)i; 
		pBMI->bmiColors[i].rgbBlue=(unsigned char)i; 
		pBMI->bmiColors[i].rgbReserved=0;
	} 
		
	for(i=0;i<GetDIBHeight();i++) {
		for(j=0;j<GetDIBWidth();j++) //放数据
			*(pDIBData+i*(GetDIBWidth()+supply)+j)=(byte)(0.299*Cnumber(i,j,2)+0.587*Cnumber(i,j,1)+0.114*Cnumber(i,j,0));//灰度值采用亮度方程计算 
		
		for(int k = 0; k<(DataByte-GetDIBWidth()); k++)//补充0
			*(pDIBData+i*supply+(i+1)*GetDIBWidth()) = (byte) 0x00;
	}	

	bih.biBitCount=8;//更改颜色深度
	pBMI->bmiHeader.biClrUsed=256;
    pBMI->bmiHeader.biSizeImage=GetDIBHeight()*DataByte;
	bfh.bfOffBits=sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + sizeof(RGBQUAD)* 256;//实际图形数据偏移量 
	bfh.bfSize=sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + sizeof(RGBQUAD)*256 + GetDIBHeight()*DataByte;//文件总大小 
	memcpy(&pBMI->bmiHeader,&bih,sizeof(BITMAPINFOHEADER)); 
	
	if(m_pBMI!=NULL)// 判断原图信息头是否为空
	delete m_pBMI;
	m_pBMI=pBMI; 

	if(m_pDIBData!=NULL)//判断原图数据是否为空
	delete m_pDIBData; 
	m_pDIBData=pDIBData; 
	
	return true; 
}

实现效果:
在这里插入图片描述

a原图       b转后
图3.12 彩色转8位灰度效果图

在这里插入图片描述

a 彩色直接转灰度后        b 本实验操作后
图3.12 24位彩色转8位灰度参数对比



第四部分 课程设计总结

4.1 遇到的问题和解决办法

  在软件的编制和调试的过程中遇到了很多问题,好在经过老师和网络资源的查找基本得以解决。

  1. 在做伽马校正的时候,由于输入的r值与不能与伽马校正公式中的r值相对应,原因是r值传送错误。经过反复修改,将传送r值的程序放在伽马校正主程序中,终于成功。

  另外,在输入参数值γ的时候,由于m_value未设置为float型,一直跳出参数输入不合法的提示,但不知道是哪出问题,经过检查参数类型才发现得以修改。

  1. 灰度图无RGB分量,故实验中需另外宏定义
    #define gray(x,y) *(m_pDIBData+(x)*GetBmpRealWidth()+y),否则部分程序结果会出错。

  2. 在做“旋转”的程序时,应合理考虑算法。如果直接旋转会出错,原因是图像像素在变换的时候,一部分像素点覆盖了另一部分像素点,导致实验结果出错。所以我在选择算法的时候做了改进,图像做一半的翻转,详细请看相应的实验清单。

  3. 还原功能我采用了两种算法:一种原理是直接保存文件,另一种原理是在刚开始接触到图像数据的时候,将数据做个备份,供还原时使用。

  4. 在做“24位真彩色图转8位灰度图”的过程中,遇到的困难较多。首先是对算法原理不够了解,其次是对如何更改图像头文件不清楚。在经过好几次的测试后,最后以失败告终。最后参考同学的程序,问题得意解决。其实原理挺简单的,最重要的是加入灰度图的颜色表。而修改头文件信息并非在原始处修改(当然我也没有找到原始头文件相关信息,希望老师能指点指点),只需要在24转8的主程序里修改即可,如何更改,需要自己再琢磨,再体会。

4.2 总结

  本次课程设计很有意义,我一直对图像处理很感兴趣,原因很简单,现象很明显,可以做很多有趣的操作。之前在冬令营中学习了图像处理的基础课程,也用AEIDK R3399板卡实践过,做了除噪、均衡化的操作,由于时间有点久了,可能也忘了很多,但经过翻看之前的笔记还是能够大致想起实现思路和步骤,所以真个编程上还算顺利。花了一个晚上,越做越有兴趣,以至于熬夜写完了所有程序。
  更重要的是,我体会到思想的重要性,正确的想法才能指导正确的操作,哪怕你对程序本身不是很了解,但是如果你有一个成形的想法,对于实验的成功也是至关重要的;而盲目教条地操作,只会让自己陷入困境。
  此次设计花了我很长时间,有成功也有失败。因为是自己独立设计,所以收获还是不小的。希望自己以后能继续保持这种钻研的精神,能够继续独立地思考,独立地设计。



第五部分 附录

Ⅰ参考文献

[1] 阮秋琦. 数字图像处理基础. 北京: 清华大学出版社,2010.09
[2] 麻志毅. 面向对象分析与设计(第2版). 北京: 北京机械工业出版社, 2013.03
[3] 温秀梅,丁学钧. Visual C++面向对象程序设计教程与实验. 北京:清华大学出版社,2014

Ⅱ 测试图片

LenaRealColorQua
24位真彩色图(方形)

8位灰度图(方形)
24位灰度图(方形)

在这里插入图片描述
24位真彩图(长方形)

在这里插入图片描述
8位灰度图(长方形)

Ⅲ 源代码

详见CSDN下载(点击链接即可下载)或者欢迎来到我的Github主页参考。

包含:此课程设计的软件开发工程全部文件、二进制解码器Binary Viewer、设计报告书(Word/PDF精美排版)。



上一篇:开篇序——一篇来自电子信息专业普通本科生的项目实践分享

下一篇:软件技术基础课程设计——数据结构与算法编程实践

    接口技术课程设计——一种基于MFC构造自动测量系统


欢迎各位订阅我,谢谢大家的点赞和专注!我会继续给大家分享我大学期间详细的实践项目。
在这里插入图片描述

发布了50 篇原创文章 · 获赞 55 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/Charmve/article/details/103792001
今日推荐