C语言数字图像处理

您所在的位置:网站首页 c语言读取pdf文件 C语言数字图像处理

C语言数字图像处理

#C语言数字图像处理| 来源: 网络整理| 查看: 265

前言

        本专栏面向所有希望或有兴趣从事数字图像处理工作、学习或研究的朋友,不懂MATLAB和OPENCV没关系,仅需要基础的C语言知识,便可以通过本专栏内容轻松入门数字图像处理方向。目前市面上的数字图像处理书籍种类繁多,往往令人眼花缭乱,不知从何而起,复杂的第三方库调用,也导致了大多数初学者苦不堪言,而本专栏内容将从繁就简,另辟蹊径,以简约明了的逻辑,无任何第三方库依赖的C语言代码,来帮助大家快速掌握,轻松入门, 这也是本专栏和作者的初衷。同时,本专栏内容的逻辑方法,并不依赖于C语言,大家也可以用同样的逻辑方法去学习其他语言的图像处理,这就是掌握学习方法的重要性。

图像像素格式

        对于初学者,往往搞不清楚,一个像素究竟是什么?针对数字图像中的位图而言,一张宽度W,高度H的图像是由W×H个像素点来表示的,每个像素都包含了各自的颜色信息,所以我们的感官才会感知到不同图像各自是什么颜色的。要有颜色的概念,我们就要先了解色彩的深度。

        色彩深度就是色彩的位数,代表了一个像素用多少个二进制位来表示颜色信息。常用的色彩深度有1位(也就是单色),2位(也就是4色CGA),4位(也就是16色VGA),8位(也就是256色),16位(增强色)以及24位和32位真彩色等。听起来对于初学者好像不容易理解,我们这里以黑白二值图、灰度图和24/32位彩色图四类来做说明。

        黑白二值单色图像:图像中每个像素点非黑即白,对于像素值非0即1,每一个像素用一个数值也就是1个二进制位即可表示(一个二进制位代表0或者1),因此,这种黑白二值图也可以叫作单色图,黑白二值图像举例如下图Fig.1所示。

                                                                                 Fig.1黑白二值图像示例

        在Fig.1中,对于任意像素P0,如果它是黑色像素,那么P0=0,反之,P0=1,这就是黑白二值图像中像素P0的数字表示。由于每个像素的数值都在0-255之间,因此,通常我们使用unsigned char类型的数组来存出每个像素的数值。对于Fig.1这张宽高为256×256大小的黑白二值图而言,我们可以用如下数组形式来存储数据:

unsigned char img[256*256]={1,1,1,....};

        8位灰度图像:8位灰度图像是指用8个bit位来表示颜色信息的图像,颜色信息范围位0-255,0是黑色,255是白色,对应的二进制位表示如下:

        0的二进制位表示:00000000

        255的二进制位表示:11111111

        8位灰度图像举例如图Fig.2所示,看起来是一张灰色的图像,但是人物细节等颜色信息明显要比单色二值图像要多很多,因为二值图像只有0和1两个颜色信息,而灰度图有0-255共256个颜色信息;

                                                                                      Fig.2 8位灰度图示例

        在Fig.2中,对于任意像素P0,如果它是黑色像素,那么P0=0,白色P0=255,其他颜色则P0在0到255之间。这就是8位灰度图像中像素P0的数字表示。由于每个像素的数值都在0-255之间,因此,通常我们依旧使用unsigned char类型的数组来存出每个像素的数值。对于Fig.2这张宽高为256×256大小的灰度图而言,我们可以用如下数组形式来存储数据:

unsigned char imggray[256*256]={255,255,255,....};

        24位彩色图像:为了表示更加丰富的彩色信息,我们基于三原色RGB,将每个像素分为了R、G和B三个颜色分量,即红色分量Red,绿色分量Green和蓝色分量Blue。同时,我们对于每个分量都使用8个二进制位也就是1个字节大小来表示它的颜色信息,对应数值范围为0-255。这样,一个像素占用3个字节,24个Bit位,也就是24位彩色图像。颜色信息则是RGB三个颜色分量的组合,由于每个分量可以表示0-255共256种颜色,因此,24位彩色图像像素共有256×256×256种颜色信息,我们也将RGB三个颜色分量叫作三个通道,举例如图Fig.3所示。 

                                                                                  Fig.3 24位彩色图像示例

        在Fig.3中,对于任意像素P0,如果它是黑色像素,那么P0=(R=0,G=0,B=0),白色P0=(R=255,G=255,B=255),通常我们用一个RGB坐标轴的三维坐标来表示,即黑色P0(0,0,0),白色P0(255,255,255)。这就是24位彩色图像中像素P0的数字表示。由于每个像素的RGB数值都在0-255之间,因此,通常我们依旧使用unsigned char类型的数组来存出每个像素的数值。对于Fig.3这张宽高为256×256大小的灰度图而言,由于每个像素有三个通道,我们可以用如下数组形式来存储数据:

unsigned char imgcolor24[256*256*3]={255,255,255,....};

        32位彩色图像:理解了24位彩色图像,那么,32位彩色图像就是在24位彩色图像的基础上添加了一个透明通道alpha位,我们经常看到一些有透明区域的图像,这些透明区域如何控制,就是依靠这个alpha通道来实现的。对于32位彩色图像的每个像素,我们使用RGBA四个颜色分量来表示,A就是透明度分量,同样占用1个字节8个bit,所以,一个像素共占用32个bit,4个字节。我们称32位彩色图像有4个通道,也就是RGBA四通道。对于黑色像素表示为(0,0,0,A),白色像素表示为(255,255,255,A),举例如图Fig.4所示。

                                                                                  Fig.4 32位彩色图像示例

        在Fig.4中,方格子区域就表示这些区域的像素透明通道是0(全透明),我们可以看到的人物区域像素的透明通道是255(不透明)。由于每个像素的RGBA数值都在0-255之间,因此,对于Fig.4这张宽高为256×256大小的灰度图而言,由于每个像素有四个通道,我们可以用如下数组形式来存储数据:

unsigned char imgcolor32[256*256*4]={255,255,255,....};

        对于上述几种格式,是我们比较常见的,而对于初学者,本文将以32位BGRA四通道位图格式为主,来教会大家如何入门数字图像处理。其他几种格式,大家可以简单理解为通道数的差别。

图像读写

        图像读写从专业角度又叫图像编解码,图像编解码是数字图像处理中的重要组成部分,甚至是一个可以单独出书的模块。由于图像格式多种多样,需要对每一种图像进行格式分析,然后单独编解码,同时还要考虑效率和质量问题,因此,也是一个难啃的骨头。对于初学者而言,想要自己实现常用图像的编解码算法,基本不太现实,常用的方法就是调用各种第三方库,比如libjpg/libpng等,或者直接使用opencv/matlab等数字图像处理库。而这些方法对于初学者而言,又是各种配置,各种依赖,苦不堪言。

        对于那些只想学下图像处理算法,并不像涉猎图像编解码,也不想花时间去使用和依赖第三方库的朋友们而言,有没有一种更好的方式,比如以简单的C语言调用来进行图像读写呢?答案是肯定的,这就是github上一份来自MIT的开源代码“stb”。

        stb的代码链接:STB图像编解码

        stb的代码中关于图像读写的部分只有两个头文件:stb_image.h和stb_image_write.h,可以实现常用图像格式如“BMP/JPG/PNG/TGA/HDR/PSD/GIF”等的编解码,而且支持从文件流和文件路径以及内存三个方式进行处理,算法进行了一定的汇编优化,最重要的是代码开源,速度快,效果好,逻辑简单!对于初学者,stb的出现真是一个不小的福音。

        为了更好的从初学者角度考虑,笔者对stb进行了二次封装,以32位bgra四通道格式基础,将stb的几种常用图像格式“BMP/JPG/PNG/TGA”编解码接口进行了合并融合,得到了如下简单的接口:

/***************************ImageFormat**************************/ enum IMAGE_FORMAT{BMP = 0, JPG, PNG, TGA}; /************************************************************ *Function: Trent_ImgBase_ImageLoad *Description: Image loading *Params: fileName-image file path,eg:"C:\\test.jpg". * width-image width. * height-image height. * component-the bits per pixel. * 1 grey * 2 grey, alpha * 3 red, green, blue * 4 red, green, blue, alpha *Return: image data. ************************************************************/ unsigned char* Trent_ImgBase_ImageLoad(char* fileName, int* width, int* height, int* component); /************************************************************ *Function: Trent_ImgBase_ImageSave *Description: Image loading *Params: fileName-image file path,eg:"C:\\save.jpg". * width-image width. * height-image height. * data-the result image data to save, with format BGRA32. * format-image format,0-BMP,1-JPG,2-PNG,3-TGA *Return: 0-OK. ************************************************************/ int Trent_ImgBase_ImageSave(char const *fileName, int width, int height, const void* data, int format);

        在上述封装代码中,我们可以看到,stb的多个接口被合并为了两个接口,Trent_ImgBase_ImageLoad图像加载和Trent_ImgBase_ImageSave图像保存接口,分别使用图像路径进行操作,简单明了,更加易用。由于stb源代码中本身对于bmp和jpg格式是返回24位三通道图像数据的,为了方便初学者学习,笔者统一将其扩充为了32位bgra格式,完整的封装代码如下:

#include"f_SF_ImgBase_RW.h" #define STB_IMAGE_IMPLEMENTATION #include "stb_image.h" #define STB_IMAGE_WRITE_IMPLEMENTATION #include "stb_image_write.h" #include #include #include inline unsigned char* f_TImageLoad(char* fileName, int* width, int* height, int* component, int redcomp) { unsigned char* tempData = stbi_load(fileName, width, height, component, redcomp); //printf("component: %d", *component); //根据像素通道数component进行判断,分别将8/24/32位转换为32bgra格式数据 if(*component == 4) { unsigned char* srcData = (unsigned char*)malloc(sizeof(unsigned char) * *width * *height * 4); unsigned char* pSrc = srcData; unsigned char* pTemp = tempData; for(int j = 0; j < *height; j++) { for(int i = 0; i < *width; i++) { pSrc[0] = pTemp[2]; pSrc[1] = pTemp[1]; pSrc[2] = pTemp[0]; pSrc[3] = pTemp[3]; pSrc += 4; pTemp += 4; } } free(tempData); return srcData; } else if(*component == 3) { unsigned char* srcData = (unsigned char*)malloc(sizeof(unsigned char) * *width * *height * 4); unsigned char* pSrc = srcData; unsigned char* pTemp = tempData; for(int j = 0; j < *height; j++) { for(int i = 0; i < *width; i++) { pSrc[0] = pTemp[2]; pSrc[1] = pTemp[1]; pSrc[2] = pTemp[0]; pSrc[3] = 255; pSrc += 4; pTemp += 3; } } free(tempData); *component = 4; return srcData; } else if(*component == 1) { unsigned char* srcData = (unsigned char*)malloc(sizeof(unsigned char) * *width * *height * 4); unsigned char* pSrc = (unsigned char*)srcData; unsigned char* pTemp = tempData; for(int j = 0; j < *height; j++) { for(int i = 0; i < *width; i++) { int gray = *pTemp++; pSrc[0] = gray; pSrc[1] = gray; pSrc[2] = gray; pSrc[3] = 255; pSrc += 4; } } free(tempData); *component = 4; return srcData; } else return NULL; }; inline int f_TImageSavePng(char const *fileName, int width, int height, int component, const void *data, int stride_in_bytes) { unsigned char* pSrc = (unsigned char*)data; for(int j = 0; j < height; j++) { for(int i = 0; i < width; i++) { int temp = pSrc[0]; pSrc[0] = pSrc[2]; pSrc[2] = temp; pSrc+=4; } } return stbi_write_png(fileName, width, height, component, data, stride_in_bytes); }; inline int f_TImageSaveBmp(char const *fileName, int width, int height, int component, const void *data) { unsigned char* pSrc = (unsigned char*)data; for(int j = 0; j < height; j++) { for(int i = 0; i < width; i++) { int temp = pSrc[0]; pSrc[0] = pSrc[2]; pSrc[2] = temp; pSrc+=4; } } return stbi_write_bmp(fileName, width, height, component, data); }; inline int f_TImageSaveTga(char const *fileName, int width, int height, int component, const void *data) { unsigned char* pSrc = (unsigned char*)data; for(int j = 0; j < height; j++) { for(int i = 0; i < width; i++) { int temp = pSrc[0]; pSrc[0] = pSrc[2]; pSrc[2] = temp; pSrc+=4; } } return stbi_write_tga(fileName, width, height, component, data); }; inline int f_TImageSaveJpg(char const *fileName, int width, int height, int component, const void *data, int quality) { unsigned char* pSrc = (unsigned char*)data; for(int j = 0; j < height; j++) { for(int i = 0; i < width; i++) { int temp = pSrc[0]; pSrc[0] = pSrc[2]; pSrc[2] = temp; pSrc+=4; } } return stbi_write_jpg(fileName, width, height, component, data, quality); }; /************************************************************ *Function: Trent_ImgBase_ImageLoad *Description: Image loading *Params: fileName-image file path,eg:"C:\\test.jpg". * width-image width. * height-image height. * component-the bits per pixel. * 1 grey * 2 grey, alpha * 3 red, green, blue * 4 red, green, blue, alpha *Return: image data. ************************************************************/ unsigned char* Trent_ImgBase_ImageLoad(char* fileName, int* width, int* height, int* component) { int redcomp = 0; return f_TImageLoad(fileName, width, height, component, redcomp); }; /************************************************************ *Function: Trent_ImgBase_ImageSave *Description: Image loading *Params: fileName-image file path,eg:"C:\\save.jpg". * width-image width. * height-image height. * data-the result image data to save, with format BGRA32. * format-image format,0-BMP,1-JPG,2-PNG,3-TGA *Return: 0-OK. ************************************************************/ int Trent_ImgBase_ImageSave(char const *fileName, int width, int height, const void* data, int format) { int component = 4; int ret = 0; //判断图像格式,根据格式进行图像保存 switch(format) { case 0://bmp ret = f_TImageSaveBmp(fileName, width, height, component, data); break; case 1://jpg ret = f_TImageSaveJpg(fileName, width, height, component, data, 100); break; case 2://png ret = f_TImageSavePng(fileName, width, height, component, data, width * 4); break; case 3://tga ret = f_TImageSaveTga(fileName, width, height, component, data); break; default: printf("Trent_SF_ImgBase_ImageSave ERROR!"); break; } return 0; };

这两个接口的调用代码如下所示:

#include "stdafx.h" #include"imgRW\f_SF_ImgBase_RW.h" int _tmain(int argc, _TCHAR* argv[]) { //定义输入图像路径 char* inputImgPath = "C://Test.jpg"; //定义输出图像路径 char* outputImgPath = "D://Test_Res.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/ unsigned char* pSrc = bgraData; for(int j = 0; j < height; j++) { for(int i = 0; i < width; i++) { int gray = (pSrc[0] + pSrc[1] + pSrc[2]) / 3; pSrc[0] = pSrc[1] = pSrc[2] = gray; pSrc += 4; } } //图像保存 int ret = Trent_ImgBase_ImageSave(outputImgPath, width, height, bgraData, JPG); free(bgraData); return 0; }

        这段测试代码中,我们使用简单的32位彩色图像灰度化效果来进行说明,对应给出测试效果图如下图5所示,简单的几行代码,快速实现了图像读写和32位彩色图像灰度化处理。

                                                                                      Fig.5 图像读写测试

        对于测试代码中,我们使用到了stride,这个概念很多初学者会产生疑惑,不知道是什么,这里给大家简单讲解一下。Stride表示图像数据在内存中的行跨度。这个行跨度并不一定是图像每一行数据的真实宽度。通常在内存中,图像的行数据是以4字节对齐的,也就是行跨度的值是4的倍数。对于32位bgra格式的图像,他的行跨度Stride=width*4,本身就是4的倍数,因此Stride与真实数据的宽度一致,不用考虑对齐问题。而对于24位rgb或bgr格式,它的每一行真实的图像数据是width*3,而这个数字并不一定是4的倍数,比如:

        一行有 11 个像素(Width = 11), 对一个 24 位(每个像素 3 字节)的图像, Stride = 11 * 3 + 3 = 36,而真实的行数据位11*3=33,这是就出现了偏差,而这个偏差值3就是扩展出来用于4字节对齐的部分。

        本文中考虑的是32位图像,大家可以忽略stride,但是,对于其他格式图像,这里我们给出一个Stride的计算公式:

        ①Stride = 每像素占用的字节数(也就是像素位数/8) * Width;

     ②如果 Stride 不是 4 的倍数, 那么 Stride = Stride + (4 - Stride mod 4);

         这里,我们给出整个工程的代码:C语言图像读写代码

        上面内容作为本专栏的第一个章节,我们用较为简单和通俗易懂的方式,来讲解了图像像素和图像读写,可能没有专业书籍那么专业,但是,笔者的宗旨是让每一个初学者能够轻松入门!

        最后,谈一下对于初学者的一些建议:对于学习图像算法,个人觉得,还是不要使用opencv和matlab的好,为什么?无论是opencv还是matlab或者其他类似的库,都只是一种图像处理工具,他们功能强大,封装了各种图像算法,但是,你在使用它的时候,往往是简单的调用它所提供的接口,而不是去了解它的具体算法,长此以往,不利于图像算法的学习。实践出真知,这才是学好算法的王道!

        本人QQ1358009172,有什么疑问欢迎相互讨论!

 

 



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3