• 位图的格式和在VS2013中使用C语言显示一幅.bmp图像的代码注释

    一.数字图像处理首先应该了解的是位图和位图的格式.

    位图就是采用位映象方法显示和存储的图象.但是其实位图可以简单的认为就是.bmp格式的图片.要注意的是.bMP文件的数据从下到上,从左到右的。

    而位图也就是.bmp格式的图片的格式(划重点)如下:

    位图文件头BITMAPFIEHEADER
    文件信息头BITMAPINFOHEADER
    调色板palette
    实际的位图数据ImageData

    下面分别介绍每个部分的结构体的构成(只需要有一个印象即可)

    第一.位图的文件头是如下的一个结构体:

    typedef struct tagBITMAPFILEHEADER {
    WORD         bfType;(文件类型由于是.bmp格式所以必须是0x424d即字符BM)
    DWORD        bfSize;(整个图像大小)
    WORD         bfReserved1;(保留字1)
    WORD         bfReserved2;(保留字2)
    DWORD        bfOffBits;(前三个部分的长度之和)
    } BITMAPFILEHEADER;
    第二.位图的信息头的结构体如下:

    typedef struct tagBITMAPINFOHEADER{
    DWORD        biSize;         //信息头的长度
    LONG         biWidth;        //图像的宽度,单位是像素(必须是4的倍数)
    LONG         biHeight;       //图像的高度,单位是像素
    WORD         biPlanes;       //必须是1
    WORD         biBitCount;     //指示颜色时要用到的位数,比如二值图就只需要一位
    DWORD        biCompression;  //是否压缩
    DWORD        biSizeImage;    //实际位图得大小
    LONG         biXPelsPerMeter;//水平分辨率
    LONG         biYPelsPerMeter;//垂直分辨率
    DWORD        biClrUsed;      //实际用到的颜色数
    DWORD        biClrImportant; //重要的颜色数
    } BITMAPINFOHEADER;


    第三.调色板的结构体:

    typedef struct tagRGBQUAD {
    BYTE    rgbBlue;     //该颜色的蓝色分量
    BYTE    rgbGreen;    //该颜色的绿色分量
    BYTE    rgbRed;      //该颜色的红色分量
    BYTE    rgbReserved; //保留值
    } RGBQUAD;


    第四.实际的位图数据

    对于真彩色图,位图数据就是存放的每个像素的R G B 值

    对于用到调色板的位图,位图数据部分存放的则是调色板的索引值.


    二.在介绍代码和其他的概念之前先介绍一下编译环境和设置:

    1.代码运行的环境是VS2013

    2.项目的创建的是win32项目.

    3.将源代码和头文件添加进项目对应的文件夹(注意添加资源文件后缀为.rc),准备工作就做好了

    4.遇到可能未初始化的指针变量,找到变量定义的地方加一个=NULL就可以了

    5.运行显示出现的窗口的标题栏可能会出现乱码.如果出现这种情况只需要将 项目->项目属性->配置属性->常规->字符集由unicode改成多字节字符集即可


    三.整个代码的结构和基本函数的分析

    在Winmain()函数里创建一个窗口,然后只要窗口不退出就循环,当接收到来自窗口的消息时,调用MainWndProc()函数处理.MainWndProc()函数里面都是通过switch()case结构来判断接收到的消息是什么,并通过调用不同的功能函数来 实现一定的功能.switch()case结构之后,调用LoadBMPFile()显示处理之后的图片.(具体的一些细节这里就不赘述了,不认识 的函数百度一下就OK)

    接下来讲一个具体的最基本的函数LoadBMPFile()

    大致思路就是:创建一个逻辑调色板,首先填充一些调色板所需要的信息,然后得到图像数据中一个指向调色板的指针,通过for循环有指针给逻辑调色板赋值,赋值之后,映射逻辑调色板到系统调色板入口.最后恢复原来的调色板即可.

    LoadBMPFile()的具体实现代码如下:

    ////////////////////////////////////////////////////////////////
    BOOL LoadBmpFile (HWND hWnd,char *BmpFileName)
    { 
    	//hWnd是一个句柄
    	//BmpFileName是图片的路径和图片的名字
       	 HFILE             	hf;
       	 LPBITMAPINFOHEADER 	lpImgData;
       	 LOGPALETTE             *pPal;
        	 LPRGBQUAD              lpRGB;
        	 HPALETTE               hPrevPalette=NULL; 
       	 HDC                    hDc;
    	 HLOCAL                 hPal;
    	 DWORD			LineBytes;
    	 DWORD 		        ImgSize;
    	 DWORD                  NumColors;
    	 DWORD                  i;
    
    	//判断图片打开是否正确,不正确则弹出一个弹框
        if((hf=_lopen(BmpFileName,OF_READ))==HFILE_ERROR){
            MessageBox(hWnd,"File c:\\test.bmp not found!","Error Message",MB_OK|MB_ICONEXCLAMATION);
            return FALSE;
    	}
    
    	//读位图数据的文件头和信息头
    	_lread(hf,(LPSTR)&bf,sizeof(BITMAPFILEHEADER)); 
    	_lread(hf,(LPSTR)&bi,sizeof(BITMAPINFOHEADER));
    
    	//用位图的宽度(单位是像素)乘以指示一个颜色要用到的位数来计算一行有多少个双字
    	LineBytes=(DWORD)WIDTHBYTES(bi.biWidth*bi.biBitCount);
    
    	//用一行的大小乘以位图的高度得出位图的位图数据的双字数
    	ImgSize=(DWORD)LineBytes*bi.biHeight;
    
    	//如果该位不为0,则biClrUsed则为实际用到的颜色数
    	//否则,颜色数为2的biBitCount次方,biBitCount为表示一个颜色所需要的位数.
            switch(bi.biBitCount){
        if(bi.biClrUsed!=0)
    		NumColors=(DWORD)bi.biClrUsed;
    	else
            switch(bi.biBitCount){
           		case 1://2色图
            	    NumColors=2;
            	    break;
    			
            	case 4://16色图
            	    NumColors=16;
            	    break;	
            	case 8://256色图
            	    NumColors=256;
            	    break;
            	case 24://真彩色图
            	    NumColors=0;
            	    break;
                  default://否则认为颜色数出错
                      MessageBox(hWnd,"Invalid color numbers!","Error Message",MB_OK|MB_ICONEXCLAMATION);
                      _lclose(hf);
                      return FALSE; 
            }
    
    	//通过计算前三部分的和是否和结构体里面的数据相等来判断前面的计算有无错误
    	if(bf.bfOffBits!=(DWORD)(NumColors*sizeof(RGBQUAD)+sizeof(BITMAPFILEHEADER)
    							+sizeof(BITMAPINFOHEADER)))
    	{
        	MessageBox(hWnd,"Invalid color numbers!","Error Message" ,MB_OK|
    	               MB_ICONEXCLAMATION);
    		_lclose(hf);
    		return FALSE; 
    	}
    
    	//计算整个位图的大小=文件头+信息头+调色板(颜色数*表示一个颜色所需的大小)+位图数据的大小
    	bf.bfSize=sizeof(BITMAPFILEHEADER)+sizeof(BITMAPINFOHEADER)+NumColors*sizeof(RGBQUAD)+ImgSize;
    
    	//分配内存,大小为信息头,调色板和位图数据的大小之和(因为文件头的内容没有更改可以直接显示)
    	//hImgData为指向该内存区域的首地址
    	if((hImgData=GlobalAlloc(GHND,(DWORD)(sizeof(BITMAPINFOHEADER)+
    						     NumColors*sizeof(RGBQUAD)+ImgSize)))==NULL)
    	{
        	MessageBox(hWnd,"Error alloc memory!","ErrorMessage",MB_OK|
                       MB_ICONEXCLAMATION);
    	    _lclose(hf);
    		return FALSE;
    	}
    
    	 //给需要操作的内存区域上锁
    	lpImgData=(LPBITMAPINFOHEADER)GlobalLock(hImgData); 
    
    	//使hf重新指向文件头的结尾即信息头的开始
       	 _llseek(hf,sizeof(BITMAPFILEHEADER),SEEK_SET);
    	//读取hf的数据块,大小为信息头+调色板+位图数据,读到lpImgData指针所指的内存块
    	_hread(hf,(char *)lpImgData,(long)sizeof(BITMAPINFOHEADER)
               +(long)NumColors*sizeof(RGBQUAD)+ImgSize);
    	//关闭
    	_lclose(hf);
    
    	//如果颜色数不等于0则说明用到了调色板
        if(NumColors!=0)
    	{                    
    		//分配一个逻辑调色板的大小的内存块
    	    hPal=LocalAlloc(LHND,sizeof(LOGPALETTE) + NumColors* sizeof(PALETTEENTRY));
    		//给需要操作的内存块上锁
    	    pPal =(LOGPALETTE *)LocalLock(hPal);
    		//设置逻辑调色板的结构体的一些值
    	    pPal->palNumEntries =(WORD) NumColors;
    		pPal->palVersion    = 0x300;
    		//将lpImagData看成基址,便宜信息头的大小的尺寸,即lpRGB指向调色板
    	    lpRGB = (LPRGBQUAD)((LPSTR)lpImgData + (DWORD)sizeof(BITMAPINFOHEADER));
    		//逐个像素的取原位图的调色板赋给逻辑调色板
    		for (i = 0; i < NumColors; i++) {
         		pPal->palPalEntry[i].peRed=lpRGB->rgbRed;
    			pPal->palPalEntry[i].peGreen=lpRGB->rgbGreen;
    			pPal->palPalEntry[i].peBlue=lpRGB->rgbBlue;
    			pPal->palPalEntry[i].peFlags=(BYTE)0;
    			lpRGB++;
    		}
    		//由逻辑调色板创建调色板
    		hPalette=CreatePalette(pPal);
    		//内存区域操作结束,解锁
    		LocalUnlock(hPal);
    		LocalFree(hPal);
    	}
    	hDc=GetDC(hWnd);
    	//如果调色板存在
    	if(hPalette){
    		//指定调色板到设备环境,返回设备环境以前的逻辑调色板
            hPrevPalette=SelectPalette(hDc,hPalette,FALSE);
    		//从当前逻辑调色板中映射调色板入口到系统调色板
    		RealizePalette(hDc);
    	}
    	//创建一个新的位图
    	hBitmap=CreateDIBitmap(hDc,	(LPBITMAPINFOHEADER)lpImgData, (LONG)CBM_INIT,
    				(LPSTR)lpImgData+sizeof(BITMAPINFOHEADER) +NumColors*sizeof(RGBQUAD),
       				(LPBITMAPINFO)lpImgData, DIB_RGB_COLORS);
    //如果以前的逻辑调色板和新的调色板都存在
    	if(hPalette && hPrevPalette){
    		//回复以前的逻辑调色板
    		SelectPalette(hDc,hPrevPalette,FALSE);
    		//释放hDc上下文环境的调色板
    		RealizePalette(hDc);
    	}
     //释放设备上下文环境
    	ReleaseDC(hWnd,hDc);
    	GlobalUnlock(hImgData);
    	return TRUE; 
    }
    


    展开全文
  • 单纯用C语言,而不是C++实现的数字图像处理。这里只是抛砖引玉,所以,只做了用C语言读入位图、保存位图等。当然包括获得位图的各种信息。 在VC6.0下,无误通过编译。
  • C语言图像处理

    2020-07-14 23:31:09
    介绍如何使用C语言实现数字图像处理的过程及数字图像处理的理论
  • C语言图像处理程序设计 非常好的图片处理资料
  • C语言图像处理教程

    2020-07-02 12:45:52
    经典的C语言图像处理教材,内容详细,深入浅出,入门必备
  • 基于C语言的数字图像处理,包括图像的灰度变换,正余弦变化,直方图等等,使用visual studio2010编写
  • C语言实用数字图像处理(日本的,看过超实用) C语言实用数字图像处理(日本的,看过超实用)
  • c语言数字图像处理

    2019-05-28 11:57:29
    转载:https://www.cnblogs.com/GoldBeetle/p/9614287.html
    展开全文
  • 图像基本变换是图像处理的基本内容,是学习以后复杂的仿射变换、透视变换以及更高级的MLS网格变形等内容的基础,意义重大。本篇将从平移、缩放和旋转三个方面来讲解如何单纯使用C语言来轻松实现这三个算法。

            本篇作为新年到来前的最后一篇,提前祝大家新年快乐!

            图像几何变换又叫做图像基本变换,主要包括图像平移、图像缩放和图像旋转几个部分,当然还有图像镜像等简单的内容。图像基本变换是图像处理的基本内容,是学习以后复杂的仿射变换、透视变换以及更高级的MLS网格变形等内容的基础,意义重大。本篇将从平移、缩放和旋转三个方面来讲解如何单纯使用C语言来轻松实现这三个算法。

    图像平移变换

    [定义与算法]

            图像平移变换可以表示为水平方向和垂直方向的位移,如果把图像坐标系的原点(0,0)点平移到(x0,y0),则图像内任意一点(x,y)平移后坐标(x’,y’)用公式表示如下:

     

            我们对测试图进行水平和垂直正方向平移100像素,效果图如图Fig.1所示。

            注意,黑色区域是我们默认填充的颜色,平移变换会出现图像跑到原图画布外面的情况,此时,原来的区域可以填充任意颜色,图像平移变换就这么简单。

    [绘制与代码]

            我们用C语言来实现图像平移变换算法,定义f_Transform.h文件,在文件中定义如下接口:

    /*********************************************************
    *Function:图像平移变换
    *Params:
    *          srcData:32bgra图像数据
    *          width:图像宽度
    *          height:图像高度
    *          stride:图像幅度,对于32bgra格式而言,stride=width*4
    *          xoffset:水平方向平移量
    *          yoffset:垂直方向平移量
    *Return:  0-成功,其他失败
    *********************************************************/
    int f_XYOfffset(unsigned char* srcData, int width, int height, int stride, int xoffset, int yofffset);

            在这个接口中,定义了两个参数xoffset和yoffset分别用来表示水平和垂直的偏移向量,注意,如果xoffset和yoffset都为正数,则表示的是向水平和垂直的正方向偏移了xoffset和yoffset个像素距离,表现在结果图中,即图像向左偏移,反之,图像向右边偏移。

            完整接口代码如下:

    /*********************************************************
    *Function:图像平移变换
    *Params:
    *          srcData:32bgra图像数据
    *          width:图像宽度
    *          height:图像高度
    *          stride:图像幅度,对于32bgra格式而言,stride=width*4
    *          xoffset:水平方向平移向量
    *          yoffset:垂直方向平移向量
    *Return:  0-成功,其他失败
    *********************************************************/
    int f_XYOfffset(unsigned char* srcData, int width, int height, int stride, int xoffset, int yoffset)
    {
    	int ret = 0;
    	unsigned char* tempData = (unsigned char*)malloc(sizeof(unsigned char) * height * stride);
    	memcpy(tempData, srcData, sizeof(unsigned char) * height * stride);
    	unsigned char* pSrc = srcData;
    	for(int j = 0; j < height; j++)
    	{
    		for(int i = 0; i < width; i++)
    		{
    			int cx = i + xoffset;
    			int cy = j + yoffset;
    			if(cx >= 0 && cx < width && cy >= 0 && cy < height)
    			{
    				int pos = cx * 4 + cy * stride;
    			    pSrc[0] = tempData[pos];
    				pSrc[1] = tempData[pos + 1];
    				pSrc[2] = tempData[pos + 2];
    			}
    			else
    			{
    				pSrc[0] = 0;
    				pSrc[1] = 0;
    				pSrc[2] = 0;
    			}
    			pSrc += 4;			
    		}
    	}
    	free(tempData);
    	return ret;
    };

            接口到这里就写完了,仅仅30行左右,下面我们来写个测试代码:

    #include "stdafx.h"
    #include"imgRW\f_SF_ImgBase_RW.h"
    #include"f_Transform.h"
    int _tmain(int argc, _TCHAR* argv[])
    {
    	//定义输入图像路径
    	char* inputImgPath = "Test.png";
    	//定义输出图像路径
    	char* outputImgPath = "res_offset.jpg";
    	//定义图像宽高信息
    	int width = 0, height = 0, component = 0, stride = 0;
    	//图像读取(得到32位bgra格式图像数据)
    	unsigned char* bgraData = Trent_ImgBase_ImageLoad(inputImgPath, &width, &height, &component);
    	stride = width * 4;
    	int ret = 0;
    	//其他图像处理操作(这里以32位彩色图像灰度化为例)
    	//////////////////////////IMAGE PROCESS/////////////////////////////
    	//图像平移
    	int xoffset = -100;//图像向右平移
    	int yoffset = -100;//图像向左平移
         //调用图像平移变换接口
    	ret = f_XYOfffset(bgraData, width, height, stride,xoffset, yoffset);
         //保存平移变换结果图
    	ret = Trent_ImgBase_ImageSave(outputImgPath, width, height, bgraData, JPG);
    	printf("f_XYOffset is finished!");
    ////////////////////////////////////////////////////////////////////
    	free(bgraData);
    }
    	return 0;

            可以看到,在这段代码中,我们使用的都是C语言标准库,对于初学者而言,非常方便,通俗易懂。

    图像缩放变换

    [定义与算法]

            图像缩放即图像缩小与放大,是图像处理中最常用的操作,可以说图像处理离不开图像缩放,好的图像缩放算法可以高清还原图像信息,对于各种复杂的图像应用而言意义重大。

            假设图像中任意一点(x,y),按照水平方向缩放比例a和垂直方向缩放比例b进行缩放,则缩放后点坐标(x’,y’)的计算如下:

                                                                                                x{}'=ax

                                                                                                y{}'=by

            当a和b小于1时,表现为缩小,大于1时表现为图像放大,等于1时不缩放;既然有了放大与缩小,就存在图像信息的删除与填充,如何进行精确计算缩放后的坐标位置?这里引入一个必需要讲解的内容---图像插值算法。

            图像插值算法有很多,从最邻近插值,到二次插值、三次插值、卷积插值等等以及超分辨率算法等高级插值,可以说,图像插值是一门学问,单独成书的也很多很多。这里,我们讲解两种最常用也是最基础的插值算法:最邻近插值和双线性二次插值。

            最邻近插值不明思义就是用距离它最近的点来代替它。如下图Fig.2所示,有A和B两个像素点,A的值为100,B的值为20,要计算AB之间的C点像素值,其中,C点距离A的距离为0.4,距离B的距离为0.6,那么,C点距离A点像素最近,则C=100,这就是最邻近插值。

            最邻近插值计算量最小,但效果较差,往往会出现锯齿问题,即缩放后的图像边缘会出现锯齿毛刺,如图Fig.3所示,左边为原图,右边为最邻近插值放大两倍的结果,可以看到字母的边缘出现了锯齿状,非常不平滑。

     

            双线性二次插值也称为一阶插值,这个算法说复杂也复杂,说简单也简单,如果你要从数学上讲明白,那要从拉格朗日插值多项式说起。

            拉格朗日插值:

            对于给定的n+1个节点,x0,x1,..xn,如果能够找到n+1个多项式l1(x),l2(x),...ln(x),满足如下条件:,

            那么,拉格朗日插值多项式P(x)表示如下:

            其中,l(x)被称作插值基函数。

            上述内容可以参考张铁所著《数值分析》136页部分,上述内容与二次插值有什么关系呢?这里我们一道例题来说明,该例题也来自张铁《数值分析》。

            对于x0和x1,它的一阶拉格朗日插值多项式L(x)求取如上所示,其中l0(x)和l1(x)分别表示对应权重,如果要计算x0到x1之间的任意一点x,那么计算L1(x)即可,看到这里我们明白,如果给定两个点,计算两点之间的任意一点插值,那么,我们只要可以使用上述1阶拉格朗日插值多项式即可,而这个方法就是二次插值的基础。

            对于双线性二次插值,我们假设要计算的插值点(x,y)的值为f(x,y),在它的附近有有f(i,j),f(i+1,j),f(i,j+1)和f(i+1,j+1)四个点,如下图Fig.4所示,f(x,y)的计算方法如下:

            ①我们使用一阶拉格朗日插值计算点(i,j)和(i+1,j)之间的点(x,j)处的插值f(x,j);

            ②我们使用一阶拉格朗日插值计算点(i,j+1)和(i+1,j+1)之间的点(x,j+1)处的插值f(x,j+1);

            ③我们使用一阶拉格朗日插值计算(x,j)和(x,j+1)之间点(x,y)处的插值f(x,y);

            这个公式就是双线性插值公式。我们用这个公式,对比最邻近插值效果,如下图Fig.5所示。

    [绘制与代码]

            有了上述算法的解析,下面我们通过C语言来实现,首先定义接口如下:

    /*********************************************************
    *Function:图像缩放变换
    *Params:
    *          srcData:32bgra图像数据
    *          width:图像宽度
    *          height:图像高度
    *          stride:图像幅度,对于32bgra格式而言,stride=width*4
    *          scaleX:水平方向缩放比例,[0,]
    *          scaleY:垂直方向缩放比例,[0,]
    *          outW:缩放结果图宽度
    *          outH:缩放结果图高度
    *          outStride:缩放结果图Stride
    *          interpolation:插值方式,0-最邻近插值,1-双线性插值
    *Return:  缩放图像bgra数据指针
    *********************************************************/
    unsigned char* f_Zoom(unsigned char* srcData, int width, int height, int stride, float scaleX, float scaleY, int* outW, int* outH, int * outStride, int interpolation);

            我们定义了f_Zoom的接口,这个接口中,由于缩放会改变图像大小,因此,我们返回一个缩放后的图像数据指针,同时,返回缩放后的图像宽高信息outW、outH和outStride;由于缩放包含水平和垂直方向的缩放因子,所以,添加水平缩放参数scaleX和垂直缩放参数scaleY,当scaleX小于1时表示水平缩小,等于1表示水平不缩放,大于1表示水平放大,垂直方向亦如此;最后,由于我们可以使用最邻近插值和双线性插值两种方式进行缩放,因此,添加了interpolation插值参数;

            有了接口,我们给出完整的接口实现代码如下:

    /*********************************************************
    *Function:图像缩放变换
    *Params:
    *          srcData:32bgra图像数据
    *          width:图像宽度
    *          height:图像高度
    *          stride:图像幅度,对于32bgra格式而言,stride=width*4
    *          scaleX:水平方向缩放比例,[0,]
    *          scaleY:垂直方向缩放比例,[0,]
    *          outW:缩放结果图宽度
    *          outH:缩放结果图高度
    *          outStride:缩放结果图Stride
    *          interpolation:插值方式,0-最邻近插值,1-双线性插值
    *Return:  缩放图像bgra数据指针
    *********************************************************/
    unsigned char* f_Zoom(unsigned char* srcData, int width, int height, int stride, float scaleX, float scaleY, int* outW, int* outH, int * outStride, int interpolation)
    {
    	int w = width * scaleX;
    	int h = height * scaleY;
    	int s = w * 4;
    	unsigned char* tempData = (unsigned char*)malloc(sizeof(unsigned char) * s * h);
    	memset(tempData, 255, sizeof(unsigned char) * s * h);
    	unsigned char* pTemp = tempData;
    	//最邻近插值
    	if(interpolation == 0)
    	{
    	    for(int j = 0; j < h; j++)
    	    {
    	    	for(int i = 0; i < w; i++)
    	    	{
    	    		int cx = CLIP3(i * width / w, 0, width - 1);
    	    		int cy = CLIP3(j * height / h, 0, height - 1);
    	    		int pos = cx * 4 + cy * stride;
    	    		pTemp[0] = srcData[pos];
    	    		pTemp[1] = srcData[pos + 1];
    	    		pTemp[2] = srcData[pos + 2];
    	    		pTemp[3] = srcData[pos + 3];
    	    		pTemp += 4;			
    	    	}
    	    }
    	}
    	else//双线性插值
    	{
    		for(int j = 0; j < h; j++)
    	    {
    	    	for(int i = 0; i < w; i++)
    	    	{
    	    		float cx = CLIP3((float)i * width / w, 1, width - 2);
    	    		float cy = CLIP3((float)j * height / h, 1, height - 2);
    				int tx = (int)cx;
    				int ty = (int)cy;
    				float p = abs(cx - tx);
    				float q = abs(cy - ty);
    	    		int pos = tx * 4 + ty * stride;
    				int p1 = pos;
    				int p2 = pos + stride;
    				int p3 = pos + 4;
    				int p4 = pos + 4 + stride;
    				float a = (1.0f - p) * (1.0f - q);
    				float b = (1.0f - p) * q;
    				float c = p * (1.0f - q);
    				float d = p * q;
    	    		pTemp[0] = CLIP3((a * srcData[p1 + 0] + b * srcData[p2 + 0] + c * srcData[p3 + 0] + d * srcData[p4 + 0]), 0, 255);
    	    		pTemp[1] = CLIP3((a * srcData[p1 + 1] + b * srcData[p2 + 1] + c * srcData[p3 + 1] + d * srcData[p4 + 1]), 0, 255);
    	    		pTemp[2] = CLIP3((a * srcData[p1 + 2] + b * srcData[p2 + 2] + c * srcData[p3 + 2] + d * srcData[p4 + 2]), 0, 255);
    	    		pTemp[3] = CLIP3((a * srcData[p1 + 3] + b * srcData[p2 + 3] + c * srcData[p3 + 3] + d * srcData[p4 + 3]), 0, 255);
    	    		pTemp += 4;			
    	    	}
    	    }
    	}
    	*outW = w;
    	*outH = h;
    	*outStride = s;
    	return tempData;
    };

            代码不足60行,通俗易懂,最后给出接口的调用代码:

    #include "stdafx.h"
    #include"imgRW\f_SF_ImgBase_RW.h"
    #include"f_Transform.h"
    int _tmain(int argc, _TCHAR* argv[])
    {
    	//定义输入图像路径
    	char* inputImgPath = "t150.png";
    	//定义图像宽高信息
    	int width = 0, height = 0, component = 0, stride = 0;
    	//图像读取(得到32位bgra格式图像数据)
    	unsigned char* bgraData = Trent_ImgBase_ImageLoad(inputImgPath, &width, &height, &component);
    	stride = width * 4;
    	int ret = 0;
    	//其他图像处理操作(这里以32位彩色图像为例)
    	//////////////////////////IMAGE PROCESS/////////////////////////////
    	//图像缩放
    	int outW, outH, outS;
    	float scaleX = 5;
    	float scaleY = 5;
    	int interpolation = INTERPOLATE_BILINEAR;//INTERPOLATE_NEAREST;
    	char* outZoomImgPath = "res_zoom.png";
    	unsigned char* pResZoom = f_Zoom(bgraData, width, height, stride, scaleX, scaleY, &outW, &outH, &outS, interpolation);
    	ret = Trent_ImgBase_ImageSave(outZoomImgPath, outW, outH, pResZoom, PNG);
    	free(pResZoom);
    	printf("f_zoom is finished!");
    	////////////////////////////////////////////////////////////////////
    	free(bgraData);
    	return 0;
    }

            效果测试如下图Fig.6所示:

    图像旋转变换

    [定义与算法]

            图像旋转即将图像按照某个原点顺时针或者逆时针旋转某个角度。

            如果平面上所有点(x,y)绕原点O旋转\Theta角度,旋转后的点为(x’,y’),则两者正向和逆向计算公式如下:

            公式的推导我们以正向变换为例,在极坐标系中,假设(x,y)到原点O距离为r,注意,这里O点表示上文平移后的图像中心点,(x,y)和原点的连线与x轴方向的夹角为b,旋转角度为a,旋转后坐标为(x’,y’),如下图Fig.7所示,则按照极坐标公式有:

            上述便是正向推导过程,即由(x,y)到旋转后的点(x’,y’)。

            在实际中,图像平面中的原点一般为左上角,也就是左上角为(0,0)点,垂直方向向下为正方向,与正常的笛卡尔坐标系不同。我们想要的往往是图像围绕图像中心点进行角度旋转,这个时候,我们需要把点(x,y)先转换为以图像中心为原点的坐标,也就是进行一定的坐标平移,然后再进行旋转变换。同时,为例避免孔洞现象,我们一般在计算的过程中,是按照逆向变换,根据目标图像素位置(x’,y’)计算它在原图中的位置(x,y),一次完成旋转变换的。

            假设旋转后图像的宽为W’,高为H’,点(x’,y’)映射到原图中的坐标为(x,y),则计算过程如下:

            ①按照平移逆变换将(x’,y’)进行平移:

            ②按照旋转逆变换公式将(x,y)进行变换:

            ③将坐标原点由图像中心平移至左上角:

            ④根据(x,y)位置选择插值算法插值得到最终旋转后的像素值;

            上面的过程就是完整的图像旋转变换,下面我们将动手实践一下。

    [绘制与代码]

            我们首先定义一个图像旋转的接口,如下:

    /*********************************************************
    *Function:图像旋转变换
    *Params:
    *          srcData:32bgra图像数据
    *          width:图像宽度
    *          height:图像高度
    *          stride:图像幅度,对于32bgra格式而言,stride=width*4
    *          angle:图像旋转角度
    *          outW:旋转结果图宽度
    *          outH:旋转结果图高度
    *          outStride:旋转结果图Stride
    *          interpolation:插值方式,0-最邻近插值,1-双线性插值
    *Return:  旋转图像bgra数据指针
    *********************************************************/
    unsigned char* f_Rotate(unsigned char* srcData, int width, int height, int stride, int angle, int* outW, int* outH, int* outStride, int interpolation);

            图像旋转变换中,图像的大小发生了变化,因此这里我们的接口返回一个变换后的图像数据指针,与缩放接口类似,添加新图像宽高输出参数outW,outH和outStride;由于旋转变换需要角度信息,因此这里添加了角度输入参数angle,范围为0到360度,同时,设置插值算法参数interpolation;

            按照前文的旋转变换算法公式,我们给出C代码如下:

    /*********************************************************
    *Function:图像旋转变换
    *Params:
    *          srcData:32bgra图像数据
    *          width:图像宽度
    *          height:图像高度
    *          stride:图像幅度,对于32bgra格式而言,stride=width*4
    *          angle:图像旋转角度
    *          outW:旋转结果图宽度
    *          outH:旋转结果图高度
    *          outStride:旋转结果图Stride
    *          interpolation:插值方式,0-最邻近插值,1-双线性插值
    *Return:  旋转图像bgra数据指针
    *********************************************************/
    unsigned char* f_Rotate(unsigned char* srcData, int width, int height, int stride, int angle, int* outW, int* outH, int* outStride, int interpolation)
    {
    	float degree = angle * PI / 180.0f;
    	float cx = 0, cy = 0, Cos = 0, Sin = 0;
    	Cos = cos(degree);
    	Sin = sin(degree);
    	//计算新图像的宽高
        int w = width * Cos + height * Sin;
    	int h = height * Cos + width * Sin;
    	int s = w * 4;
    	*outW = w;
    	*outH = h;
    	*outStride = s;
    	//常量计算,用来优化速度
    	cx = -w / 2.0f * Cos - h / 2.0f * Sin + width / 2.0f;
    	cy = w / 2.0f * Sin - h / 2.0f * Cos + height / 2.0f;
    	unsigned char* tempData = (unsigned char*)malloc(sizeof(unsigned char) * s * h);
    	memset(tempData, 255, sizeof(unsigned char) * s * h);
    	unsigned char* pTemp = tempData;
    	//最邻近插值
    	if(interpolation == 0)
    	{
    	    for(int j = 0; j < h; j++)
    	    {
    	    	for(int i = 0; i < w; i++)
    	    	{
    				//这里实际上就是按照公式计算,进行了优化,把一些常量计算放到了外面的cx和cy中
    	    		int tx = i * Cos + j * Sin + cx;
    	    		int ty = j * Cos - i * Sin + cy;
    				if(tx >= 0 && tx < width && ty >= 0 && ty < height)
    				{
    	    		    int pos = tx * 4 + ty * stride;
    	    		    pTemp[0] = srcData[pos];
    	    		    pTemp[1] = srcData[pos + 1];
    	    		    pTemp[2] = srcData[pos + 2];
    	    		    pTemp[3] = srcData[pos + 3];
    				}
    				else
    				{
    					pTemp[0] = 0;
    	    		    pTemp[1] = 0;
    	    		    pTemp[2] = 0;
    	    		    pTemp[3] = 255;
    				}
    	    		pTemp += 4;			
    	    	}
    	    }
    	}
    	else//双线性插值
    	{
    		for(int j = 0; j < h; j++)
    	    {
    	    	for(int i = 0; i < w; i++)
    	    	{
    				//这里实际上就是按照公式计算,进行了优化,把一些常量计算放到了外面的cx和cy中
                    float mx = i * Cos + j * Sin + cx;
    	    		float my = j * Cos - i * Sin + cy;
    				if(mx >= 0 && mx < width && my >= 0 && my < height)
    				{
    				    int tx = (int)mx;
    				    int ty = (int)my;
    				    float p = abs(mx - tx);
    				    float q = abs(my - ty);
    	    		    int pos = tx * 4 + ty * stride;
    				    int p1 = pos;
    				    int p2 = pos + stride;
    				    int p3 = pos + 4;
    				    int p4 = pos + 4 + stride;
    				    float a = (1.0f - p) * (1.0f - q);
    				    float b = (1.0f - p) * q;
    				    float c = p * (1.0f - q);
    				    float d = p * q;
    	    		    pTemp[0] = CLIP3((a * srcData[p1 + 0] + b * srcData[p2 + 0] + c * srcData[p3 + 0] + d * srcData[p4 + 0]), 0, 255);
    	    		    pTemp[1] = CLIP3((a * srcData[p1 + 1] + b * srcData[p2 + 1] + c * srcData[p3 + 1] + d * srcData[p4 + 1]), 0, 255);
    	    		    pTemp[2] = CLIP3((a * srcData[p1 + 2] + b * srcData[p2 + 2] + c * srcData[p3 + 2] + d * srcData[p4 + 2]), 0, 255);
    	    		    pTemp[3] = CLIP3((a * srcData[p1 + 3] + b * srcData[p2 + 3] + c * srcData[p3 + 3] + d * srcData[p4 + 3]), 0, 255);
    				}
    				else
    				{
    					pTemp[0] = 0;
    	    		    pTemp[1] = 0;
    	    		    pTemp[2] = 0;
    	    		    pTemp[3] = 255;
    				}
    				pTemp += 4;
    	    	}
    	    }
    	}
    	return tempData;
    };

            本文的代码可以看到,基本都是将插值的interpolation条件判断放到了循环外面,导致代码段较长,实际上这样做是为了优化速度,增强代码可读性,大家可以体会一下。

            我们对上面接口进行调用测试如下:

    #include "stdafx.h"
    #include"imgRW\f_SF_ImgBase_RW.h"
    #include"f_Transform.h"
    int _tmain(int argc, _TCHAR* argv[])
    {
    	//定义输入图像路径
    	char* inputImgPath = "Test.png";
    	//定义图像宽高信息
    	int width = 0, height = 0, component = 0, stride = 0;
    	//图像读取(得到32位bgra格式图像数据)
    	unsigned char* bgraData = Trent_ImgBase_ImageLoad(inputImgPath, &width, &height, &component);
    	stride = width * 4;
    	int ret = 0;
    	//其他图像处理操作(这里以32位彩色图像为例)
    	//////////////////////////IMAGE PROCESS/////////////////////////////
    	//图像旋转
    	int outW, outH, outS;
    	int angle = 80;
    	int interpolation = INTERPOLATE_NEAREST;//INTERPOLATE_BILINEAR;//INTERPOLATE_NEAREST;
    	char* outRotateImgPath = "res_rotate_nearest.jpg";
    	unsigned char* pResRotate = f_Rotate(bgraData, width, height, stride, angle, &outW, &outH, &outS, interpolation);
    	ret = Trent_ImgBase_ImageSave(outRotateImgPath, outW, outH, pResRotate, JPG);
    	free(pResRotate);
    	printf("f_Rotate is finished!");
    	////////////////////////////////////////////////////////////////////
    	free(bgraData);
    	return 0;
    }

            大家可以看到,调用非常方便,甚至比opencv更加通俗易懂。最后我们给出对应的测试效果,如图Fig.8所示。

            本节完整的代码工程关注本人公众号“SF图像算法”有相关下载链接即可免费下载。

    [知识扩展]

            在本文中,我们详细了解了图像平移、图像缩放和图像旋转三种图像几何变换,也是最常用的图像基本变换。在这个过程中我们是单个一一讲解的,为的是让新手同学们能够各个击破,单独理解。而实际中,这三个变换可以通过一个仿射变换矩阵来统一表达,放这边还矩阵如下:

            其中,s表示缩放比例,tx和ty表示平移量,theta表示角度;

            按照这个公式,我们可以一次计算出三种变换后对应的结果(x’,y’),然后再进行插值计算即可,大家可以自己尝试一下;

            对于图像变形和插值算法,多少年来,有无数相关的论文研究,近几年来,随着深度学习的飞速发展,基于卷积神经网络的超分辨率,图像复原以及图像变形的算法也是层出不穷,可见其意义之大!

            最后,学海无涯,共勉!

     

     

    展开全文
  • 本篇介绍数字图像处理中的图像直方图,读完本文,您将达到仅仅使用C语言来绘制一张图像的灰度直方图和彩色直方图,而完全不用依赖任何第三方库。

            本篇介绍数字图像处理中的图像直方图,读完本文,您将达到仅仅使用C语言来绘制一张图像的灰度直方图和彩色直方图,而完全不用依赖任何第三方库。

    [定义与算法]

    [直方图定义]

            直方图包括灰度直方图和彩色直方图两类,如果把灰度直方图看作是灰度通道的直方图,那么,彩色直方图可以看作是R/G/B三通道的独立直方图。

            灰度直方图描述的是图像中该灰度级对应的像素个数,也就是像素的统计信息。所谓的灰度级,就是像素的取值范围,通常为0-255,共256个值,因此对应256个灰度级。

            我们如何绘制直方图呢?如果我们以横坐标表示灰度级,纵坐标表示该灰度级像素的个数,以下图Fig.1所示测试图为例来做说明。

                                                                                               Fig.1 直方图示意图

            图中左边Test为一张宽高4×4大小的灰度图,不过我们使用了彩色来区分不同的灰度值。我们以横坐标表示灰度级,纵坐标表示像素个数,用对应颜色来区分不同灰度级,在Test中,我们统计不同灰度级对应的像素个数,如下所示:

            灰度级为2的像素有3个;

            灰度级为3的像素有1个;

            灰度级为4的像素有1个;

            灰度级为5的像素有4个;

            灰度级为6的像素有1个;

            灰度级为8 的像素有4个;

            灰度级为9的像素有2个;

            我们在右边坐标系中,用对应的颜色进行填充,每个小方格表示一个像素个数,这样就得到了一张高低不同的统计图,这种统计图就叫做直方图,从直方图中,我们可以很明了的看出,一张图中有多少个像素灰度级,每个灰度级有多少像素,进而可以根据直方图计算出对应的均值和方差等信息。

            直方图可以反应一张图的像素灰度级分布情况,但是无法反应图像的内容,一张直方图可能对应一张原始图像,也可能对应多张原始图像,图像内容不同,但像素对应灰度级分布相同。我们以图Fig.2为例,左右两张图,图A和图B内容是不一样的,但是,他们的灰度直方图分布却是一摸一样,正是因为他们每个灰度级像素数目是一样的。

            

                                                      (a)灰度图A                                      (b)灰度图B

                                                                                        Fig.2 灰度直方图特例举例

    [直方图特点]

             直方图具有平移、旋转和缩放不变性的特点。对于平移图像,旋转图像角度的情况下,图像操作前后的直方图分布不变,对于缩放图像,前后直方图的分布也基本不变,如图Fig.3所示。

                  (a)原图直方图                         (b)平移图像直方图               (c)旋转图像直方图                   (d)放大图像直方图

                                                                       Fig.3 直方图平移旋转缩放不变性示意图

            正是由于直方图这些特性,使得直方图在图像分割、图像分类和图像检索以及图像识别中意义重大。

    [绘制与代码]

    [直方图绘制]

            了解了直方图的定义,我们就可以用简单的C语言来绘制出图像的直方图。我们以灰度直方图为例,具体步骤如下:

            ①定义一个一维数组grayLut[256],初始化为0,用来存储直方图统计信息;

    int grayLut[256] = {0};

            ②遍历图像每个像素,计算像素灰度值gray,使用明度灰度计算公式,如下:

                                                                                          gray=(R+G+B)/3

            ③直方图对应灰度级累加:

             grayLut[gray]++;

             实际上到这一步,我们已经统计出每一个灰度级对应的像素数,也就是直方图信息,后面的步骤是如何将其形象的绘制出来。

             ④计算最大灰度级对应像素数max;

    for(int i = 0; i < 256; i++)
    {
    	max = MAX2(max, grayLut[i]);
    }

            ⑤定义一张宽256高100的空白图像,按照最大值max重新映射每个灰度级对应像素数;

            这一步是为了避免灰度级像素数过大或者过小,导致无法绘制问题,这里,统一将其归一化到高度为100的图像内,也就是100的高度对应的是max个像素数量:

    int sum = CLIP3(maxUnit * hHeight * grayLut[i] / max, 0, 100);

            注意这里的maxUnit是一个调节因子,如果maxUnit=1就是正常的直方图,如果maxUnit=5就表示像素数统计扩大了5倍,这样做,仅仅是为了更方便的视图,有的图像中像素灰度级过于单一,会导致直方图信息很小,无法看清楚,maxUnit就是为了解决这个问题而设置的缩放参数。

            有了上述步骤,我们给出完成的C代码,该代码包含了灰度通道和红绿蓝三通道的直方图绘制:

    #include"f_Histagram.h"
    
    /************************************************************
    *Function:  Histagram
    *Description: Image loading
    *Params:    srcData-32bgra image data.
    *           width-image width.
    *           height-image height.
    *           stride-image stride.
    *           mode-0-1-2-3-gray-red-green-blue channel histagram 
    *Return:    histagram image data, image size[256x100].
    ************************************************************/
    unsigned char* f_Histagram(unsigned char* srcData, int width, int height, int stride, int mode)
    {
    	unsigned char*pSrc = srcData;
    	int hWidth = 256;
    	int hHeight = 100;
    	int maxUnit = 5;
    	int hStride = 256 * 4;
    	unsigned char* histData = (unsigned char*)malloc(sizeof(unsigned char) * hHeight * hStride);
    	memset(histData, 255, sizeof(unsigned char) * hHeight * hStride);
    	unsigned char* pHist = histData;
    	int max = 0;
    	if(mode == CHANNEL_GRAY)//灰度直方图
    	{
            int grayLut[256] = {0};
    		for(int j = 0; j < height; j++)
    		{
    			for(int i = 0; i < width; i++)
    			{
    				int gray = (pSrc[0] + pSrc[1] + pSrc[2]) / 3;
    				grayLut[gray] ++;
    				pSrc += 4;
    			}
    		}
    		for(int i = 0; i < 256; i++)
    		{
    			max = MAX2(max, grayLut[i]);
    		}
    		for(int i = 0; i < hWidth; i++)
    		{
    			int sum = CLIP3(maxUnit * hHeight * grayLut[i] / max, 0, 100);
    			for(int j = 0; j < sum; j++)
    			{
    			    int pos = i * 4 + (hHeight - 1 - j) * hStride;
    				histData[pos] = 128;
    				histData[pos + 1] = 128;
    				histData[pos + 2] = 128;
    			}
    		}
    	}
    	else if(mode == CHANNEL_RED)//红色(Red)通道直方图
    	{
    		int grayLut[256] = {0};
    		for(int j = 0; j < height; j++)
    		{
    			for(int i = 0; i < width; i++)
    			{
    				int gray = pSrc[2];//PC上一般通道顺序位bgra排列
    				grayLut[gray] ++;
    				pSrc += 4;
    			}
    		}
    		for(int i = 0; i < 256; i++)
    		{
    			max = MAX2(max, grayLut[i]);
    		}
    		for(int i = 0; i < hWidth; i++)
    		{
    			int sum = CLIP3(maxUnit * hHeight * grayLut[i] / max, 0, 100);
    			for(int j = 0; j < sum; j++)
    			{
    			    int pos = i * 4 + (hHeight - 1 - j) * hStride;
    				histData[pos] = 0;
    				histData[pos + 1] = 0;
    				histData[pos + 2] = 255;
    			}
    		}
    	}
    	else if(mode == CHANNEL_GREEN)//绿色(Green)通道直方图
    	{
    		int grayLut[256] = {0};
    		for(int j = 0; j < height; j++)
    		{
    			for(int i = 0; i < width; i++)
    			{
    				int gray = pSrc[1];//PC上一般通道顺序位bgra排列
    				grayLut[gray] ++;
    				pSrc += 4;
    			}
    		}
    		for(int i = 0; i < 256; i++)
    		{
    			max = MAX2(max, grayLut[i]);
    		}
    		for(int i = 0; i < hWidth; i++)
    		{
    			int sum = CLIP3(maxUnit * hHeight * grayLut[i] / max, 0, 100);
    			for(int j = 0; j < sum; j++)
    			{
    			    int pos = i * 4 + (hHeight - 1 - j) * hStride;
    				histData[pos] = 0;
    				histData[pos + 1] = 255;
    				histData[pos + 2] = 0;
    			}
    		}
    	}
    	else if(mode == CHANNEL_BLUE)//蓝色(blue)通道直方图
    	{
    		int grayLut[256] = {0};
    		for(int j = 0; j < height; j++)
    		{
    			for(int i = 0; i < width; i++)
    			{
    				int gray = pSrc[0];//PC上一般通道顺序位bgra排列
    				grayLut[gray] ++;
    				pSrc += 4;
    			}
    		}
    		for(int i = 0; i < 256; i++)
    		{
    			max = MAX2(max, grayLut[i]);
    		}
    		for(int i = 0; i < hWidth; i++)
    		{
    			int sum = CLIP3(maxUnit * hHeight * grayLut[i] / max, 0, 100);
    			for(int j = 0; j < sum; j++)
    			{
    			    int pos = i * 4 + (hHeight - 1 - j) * hStride;
    				histData[pos] = 255;
    				histData[pos + 1] = 0;
    				histData[pos + 2] = 0;
    			}
    		}
    	}
    	else
    		return NULL;
    	return histData;
    };

            调用方式代码如下:

    // Trent_ImageRWDemo.cpp : Defines the entry point for the console application.
    //
    
    #include "stdafx.h"
    #include"imgRW\f_SF_ImgBase_RW.h"
    #include"f_Histagram.h"
    int _tmain(int argc, _TCHAR* argv[])
    {
    	//定义输入图像路径
    	char* inputImgPath = "F://Trent//C数字图像处理//1.2图像直方图//Trent_ImageRWDemo//Trent_ImageRWDemo//H2.png";
    	//定义输出图像路径
    	char* outputImgPath_gray = "F://Trent//C数字图像处理//1.2图像直方图//Trent_ImageRWDemo//Trent_ImageRWDemo//hist_gray.jpg";
    	char* outputImgPath_red = "F://Trent//C数字图像处理//1.2图像直方图//Trent_ImageRWDemo//Trent_ImageRWDemo//hist_red.jpg";
    	char* outputImgPath_green = "F://Trent//C数字图像处理//1.2图像直方图//Trent_ImageRWDemo//Trent_ImageRWDemo//hist_green.jpg";
    	char* outputImgPath_blue = "F://Trent//C数字图像处理//1.2图像直方图//Trent_ImageRWDemo//Trent_ImageRWDemo//hist_blue.jpg";
    	//定义图像宽高信息
    	int width = 0, height = 0, component = 0, stride = 0;
    	//图像读取(得到32位bgra格式图像数据)
    	unsigned char* bgraData = Trent_ImgBase_ImageLoad(inputImgPath, &width, &height, &component);
    	stride = width * 4;
    	//其他图像处理操作(这里以32位彩色图像灰度化为例)
    	//////////////////////////IMAGE PROCESS/////////////////////////////
    	//计算各个通道直方图
    	int hWidth = 256;
    	int hHeight = 100;
    	int hStride = 256 * 4;
    	//灰度直方图
    	int mode = CHANNEL_GRAY;
    	unsigned char* histData = NULL;
    	//灰度直方图
    	histData = f_Histagram(bgraData, width, height, stride, mode);
    	int ret = Trent_ImgBase_ImageSave(outputImgPath_gray, hWidth, hHeight, histData, JPG);
    	//红色通道直方图
    	mode = CHANNEL_RED;
    	histData = f_Histagram(bgraData, width, height, stride, mode);
    	ret = Trent_ImgBase_ImageSave(outputImgPath_red, hWidth, hHeight, histData, JPG);
    	//绿色通道直方图
    	mode = CHANNEL_GREEN;
    	histData = f_Histagram(bgraData, width, height, stride, mode);
    	ret = Trent_ImgBase_ImageSave(outputImgPath_green, hWidth, hHeight, histData, JPG);
    	//蓝色通道直方图
    	mode = CHANNEL_BLUE;
    	histData = f_Histagram(bgraData, width, height, stride, mode);
    	ret = Trent_ImgBase_ImageSave(outputImgPath_blue, hWidth, hHeight, histData, JPG);
    	////////////////////////////////////////////////////////////////////
    	free(bgraData);
    	free(histData);
    	return 0;
    }
    
    

            整体上,上述代码仅仅使用了最简单的C语言知识,不依赖任何第三方库,更加方便初学者理解和学习。

            测试效果图如下图Fig.4所示,左边为原图32位bgra图像,右边从上到下依次位灰度直方图,红色通道直方图,绿色通道直方图和蓝色通道直方图:

                                                                                                  Fig.4 图像直方图示例

    [知识扩展]

            直方图在图像处理中具有很重要的作用,在图像分类,分割等多方面应用广泛,本文只是做了最简单的介绍,为初学者必修内容,对于直方图扩展部分,可以了解直方图匹配,直方图均衡化以及基于直方图的各种图像增强等部分内容。

            直方图还有一个需要大家注意的地方,对于PHOTOSHOP很熟悉的人也会经常用到,我们可以通过直方图来判断一张图像是否偏暗、偏亮或者光线正常,如下图Fig.5所示,我们将直方图按照灰度级进行区域划分,分为阴影-暗部、中间调、和高光-亮部三个区域,哪个区域的像素统计数量较多,也就是图中黑色面积较多,那么,图像就偏向那个区域。

                                                                                                  Fig.5 直方图判断示意图

            以图Fig.6为例,图(a)阴影区域过大,那么,图像整体就偏暗,图(c)高光区域偏大,那么图像就偏亮或者过曝。通过这些判断,我们就可以对不同的图做出不同的亮度对比度调节,来修正图像,还原完美的照片!

                                                             (a)                                   (b)                                      (c)

                                                                                            Fig.6 直方图判断举例图

            最后,本小节完整代码工程链接:C图像直方图

            下望大家努力学习,共勉!

    展开全文
  • C语言图像处理代码

    2020-07-15 10:17:43
    各种c语言处理图像的代码,中值滤波、直方图处理、旋转平移等等
  • 本文将介绍数字图像处理中两个基本内容---图像亮度对比度,详细讲解两种基本的亮度对比度调节算法,并使用C语言进行算法实现,在教会初学者算法的同时,不依赖任何第三方库编程实现,学以致用且易学易用。

            本文将介绍数字图像处理中两个基本内容---图像亮度对比度,详细讲解两种基本的亮度对比度调节算法,并使用C语言进行算法实现,在教会初学者算法的同时,不依赖任何第三方库编程实现,学以致用且易学易用。

            图像亮度、对比度和饱和度是图像处理中三个基本概念,本片将着重介绍一下亮度和对比度。

            图象亮度是指画面的明亮程度,单位是堪德拉每平米(cd/m2)或称nits,对于一副灰度图而言,灰度值越高,图像就越亮,反之,图像越暗;

            如图Fig.1所示,图像亮度由左向右一次增加,图像视觉感受依次变亮。

            图像对比度是指一幅图像中明暗区域最亮的白和最暗的黑之间不同亮度层级的测量,即一幅图像灰度反差的大小或者图片上亮区域和暗区域的层次感

            如图Fig.2所示,图像的对比度由左向右依次增强,灰度级反差依次增强;

    [定义与算法]

            图像亮度对比度调节算法有很多,累计的研究论文也是数不胜数,本文从初学者的角度,来介绍两种较为简单、实用的算法。在介绍算法之前,我们首先假设输入图像像素为P(x),输出结果像素为D(x)。

            算法一:线性亮度对比度调节算法,公式如下:

            其中,k为大于0的数,用于调节对比度,当0<k<1时对比度减小,当k=1时,保持原始对比度,当k>1时,对比度增强;l用于调节图像亮度,范围为[-255,255];

            为了方便调节参数,我们将k和l进行了一定的限制和修正,公式如下:

            这个公式允许我们调节亮度(bright)和对比度(contrast)的范围都是[-100,100],在实际中,更加便于理解和调节。我们用这个算法公式进行效果测试,如下图Fig.3所示。

            上述算法一是一个最简单的线性亮度对比度调节算法,当然,也存在一些问题,比如,在调节对比度的同时,亮度也发生了较大变化等。

            算法二:Photoshop旧版亮度对比度调节算法,公式如下:

            这个算法是Photoshop中的经典算法,Photoshop经历了N次版本更新,但是,这个亮度对比度调节算法依旧保留至今。算法详细过程如下:

            ①亮度调节算法使用的是最简单的线性调节,即:

            ②算法首先根据对比度contrast进行判断,对比度大于0,则先进行亮度调节,反之,先进行对比度调节;

            ③当对比度参数contrast>0时,进行如下对比度调节:

            此处如果contrast=100,则做如下变换;

            ④当对比度参数contrast<=0时,进行如下对比度调节:

            此处如果contrast=-100,则D(x)=0;

            在整个算法中,采用了一个默认阈值threshold=127.5,在contrast=-100时,对比度效果表现为中性灰颜色,我们测试这个算法,并与Photoshop进行效果对比,如图Fig.4所示。

            通过图Fig.4的对比,我们发现,本文算法与Photoshop的旧版亮度对比度调节算法效果基本一致。

    [绘制与代码]

            我们用C语言来实现图像亮度对比度算法,定义如下接口:

    /*********************************************************
    *Function:图像亮度对比度算法一
    *Params:
    *          srcData:32bgra图像数据
    *          width:图像宽度
    *          height:图像高度
    *          stride:图像幅度,对于32bgra格式而言,stride=width*4
    *          brightness: 亮度值,范围[-100,100]
    *          contrast: 对比度值,范围[-100,100]
    *Return:  0-成功,其他失败
    *********************************************************/
    int f_BrightContrastLineartransform(unsigned char* srcData, int width, int height, int stride, int brightness, int contrast);
    /*********************************************************
    *Function:图像亮度对比度算法二
    *Params:
    *          srcData:32bgra图像数据
    *          width:图像宽度
    *          height:图像高度
    *          stride:图像幅度,对于32bgra格式而言,stride=width*4
    *          brightness: 亮度值,范围[-100,100]
    *          contrast: 对比度值,范围[-100,100]
    *Return:  0-成功,其他失败
    *********************************************************/
    int f_BrightContrastPS(unsigned char* srcData, int width, int height, int stride, int brightness, int contrast);
    

            在该接口中,我们统一使用bgra32位数据格式,设置亮度brightness和对比度contrast两个调节参数,分别实现算法一和算法二两种算法,实现代码如下:

    #include"f_Colortransform.h"
    /*********************************************************
    *Function:明度法灰度化
    *Params:
    *          srcData:32bgra图像数据
    *          width:图像宽度
    *          height:图像高度
    *          stride:图像幅度,对于32bgra格式而言,stride=width*4
    *          brightness: 亮度值,范围[-100,100]
    *          contrast: 对比度值,范围[-100,100]
    *Return:  0-成功,其他失败
    *********************************************************/
    int f_BrightContrastLineartransform(unsigned char* srcData, int width, int height, int stride, int brightness, int contrast)
    {
    	int ret = 0;
    	unsigned char* pSrc = srcData;
    	for(int j = 0; j < height; j++)
    	{
    		for(int i = 0; i < width; i++)
    		{
    			//pSrc[0]---Blue色蓝通道,pSrc[1]---Green色绿通道,pSrc[2]---Red红色通道
    			pSrc[0] = CLIP3((contrast + 100) * pSrc[0] / 100 + brightness, 0, 255);
    			pSrc[1] = CLIP3((contrast + 100) * pSrc[1] / 100 + brightness, 0, 255);
    			pSrc[2] = CLIP3((contrast + 100) * pSrc[2] / 100 + brightness, 0, 255);
    			//32位bgra格式,每个像素有4个字节表示,所以内存中每次偏移4表示一个像素
    			pSrc += 4;
    		}
    	}
    	return ret;
    };
    /*********************************************************
    *Function:图像亮度对比度算法二
    *Params:
    *          srcData:32bgra图像数据
    *          width:图像宽度
    *          height:图像高度
    *          stride:图像幅度,对于32bgra格式而言,stride=width*4
    *          brightness: 亮度值,范围[-100,100]
    *          contrast: 对比度值,范围[-100,100]
    *Return:  0-成功,其他失败
    *********************************************************/
    int f_BrightContrastPS(unsigned char* srcData, int width, int height, int stride, int brightness, int contrast)
    {
    	int ret = 0;
    	unsigned char* pSrc = srcData;
    	float threshold = 127.5;
    	unsigned char LUT_BC[256] = {0};
    	int temp = 0;
    	for (int i = 0; i < 256; i++)
    	{
    		if (contrast > 0)
    		{ 
    			temp = CLIP3(i + brightness, 0, 255);
    			if (contrast < 100)
    				temp = CLIP3(threshold + (temp - threshold) * (1.0f / (1.0f - contrast / 100.0f)), 0, 255);
    			else
    				temp = temp > threshold ? 255 : 0;
    		}
    		else
    		{ 
    			temp = i; 
    			temp = CLIP3(threshold + (temp - threshold) * (1.0f + contrast / 100.0f), 0, 255);
    			temp = CLIP3(temp + brightness, 0, 255);
    		} 
    		LUT_BC[i] = temp;
    	}
    	for (int j = 0; j < height; j++)
    	{
    		for (int i = 0; i < width; i++)
    		{
    			//pSrc[0]---Blue色蓝通道,pSrc[1]---Green色绿通道,pSrc[2]---Red红色通道
    			pSrc[0] = LUT_BC[pSrc[0]];
    			pSrc[1] = LUT_BC[pSrc[1]];
    			pSrc[2] = LUT_BC[pSrc[2]];
    			//32位bgra格式,每个像素有4个字节表示,所以内存中每次偏移4表示一个像素
    			pSrc += 4;
    		}
    	}
    	return ret;
    };

            整个代码非常简单,没有使用复杂的C语言代码,方便初学者学习,最后,我们给出对应的调用代码:

    // Trent_ImageRWDemo.cpp : Defines the entry point for the console application.
    //
    
    #include "stdafx.h"
    #include"imgRW\f_SF_ImgBase_RW.h"
    #include"f_Colortransform.h"
    
    int _tmain(int argc, _TCHAR* argv[])
    {
    	//定义输入图像路径
    	char* inputImgPath = "Test.png";
    	//定义输出图像路径
    	char* outputImgPath_bc = "res_bc.jpg";
    	
    	//定义图像宽高信息
    	int width = 0, height = 0, component = 0, stride = 0;
    	//图像读取(得到32位bgra格式图像数据)
    	unsigned char* bgraData = Trent_ImgBase_ImageLoad(inputImgPath, &width, &height, &component);
    	stride = width * 4;
    	//其他图像处理操作(这里以32位彩色图像灰度化为例)
    	//////////////////////////IMAGE PROCESS/////////////////////////////
    	////彩色图像线性亮度对比度调节
    	int brightness = -30;
    	int contrast = 100;
    	int ret = 0;
    	//算法一
    	//ret = f_BrightContrastLineartransform(bgraData, width, height, stride, brightness, contrast);
    	//算法二Photoshop旧版亮度对比度调节
    	ret = f_BrightContrastPS(bgraData, width, height, stride, brightness, contrast);
    	ret = Trent_ImgBase_ImageSave(outputImgPath_bc, width, height, bgraData, JPG);
    	printf("Done!");
    	////////////////////////////////////////////////////////////////////
    	free(bgraData);
    	return 0;
    }
    

    [知识扩展]

            亮度对比度作为图像处理的基本内容,本文所讲述的都是带参数的调节算法,实际应用中,它的研究重点在于如何自动对一张图像进行亮度对比度调节,而不是人工输入参数进行调节,如下图Fig.5所示,上面一组位原图,光线和对比度都因暗光拍摄环境而有所影响,下面一组图为自动亮度对比度调节算法的效果图,可以看到,亮度和内容的对比度层次感明显提升。

            目前,亮度对比度自动调节算法主要可以分为传统算法和深度学习算法两大类。传统算法主要包括颜色直方图裁剪、直方图均衡化、Retinex增强以及局部均方差信息和曲线拟合等方法,对应论文举例如下:

    《Real-Time Adaptive Contrast Enhancement》

    《Multiscale Retinex》

    《A Novel Automatic White Balance Method For Digital Still Cameras》

    《Automatic Exposure Correction of Consumer Photographs》

            深度学习算法往往基于卷积神经网络、GAN网络等,构造各种亮度对比度参数调节模型或者端到端模型,其中效果最佳的一篇论文,个人认为当属腾讯2019年发表的一篇论文,效果惊艳,而且GPU可以实时处理,大家有兴趣可以自行研究,论文及对应代码如下:

    《Underexposed Photo Enhancement using Deep Illumination Estimation》

    论文的测试DEMO github地址:https://github.com/wangruixing/DeepUPE

            本文到此就讲完了,主要给大家讲述了两种基础的图像亮度对比度调节算法,并给出了完整的C语言实现代码,手把手教会大家单纯用C语言来学图像算法,就这么简单!

            本节完整的代码工程关注本人公众号“SF图像算法”有相关下载链接即可免费下载。

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

    展开全文
  • 本文对C语言数字图像处理系列文章进行了总结 ,同时,后续进阶内容进行了规划,帮助大家更上一层楼!
  • 图像处理C语言程序

    2020-07-13 23:30:25
    数字图像处理大多是MATLAB软件来进行处理 但是同时也可以用C语言来进行编程及实现。
  • 本章介绍图像处理领域很重要的一个部分“图像噪声”,我们将常见的几种噪声进行分析讲解,并通过C语言来实现图像噪声的添加,同时也为后续图像平滑去噪章节做好铺垫,内容简洁,编程不需要复杂的代码逻辑,通俗...
  • BMP图像用C语言实现图像的缩放。缩放的倍数通过输入设定 #include &lt;Windows.h&gt; #include &lt;stdlib.h&gt; #include &lt;stdio.h&gt; #include &lt;math.h&gt; int ...
  • 在LINUX下用C语言进行图像处理,教程通俗易懂
  • 使用c语言实现图像的二值化操作,功能类似于opencv的cvthreashold函数,阈值可自己设置
  • 细化(thinning)算法有很多,我们在这里介绍的是一种简单而且效果很好的算法,它就能够实现从文本抽取骨架的功能。我们的对象是白纸黑字的文本,但在程序中为了处理的方便,还是采用 256 级灰度图,不过只用到了...
  • 头文件:graphics.h (一) 像素函数 56. putpiel() 画像素点函数 57. getpixel()返回像素色函数 (二) 直线和线型函数 58. line() 画线函数 59. lineto() 画线函数 ...62. getlinesettings() 获
1 2 3 4 5 ... 20
收藏数 31,844
精华内容 12,737
关键字:

用c语言对图像处理