C语言数字图像处理---1.1图像的像素格式与图像读写

news/2025/1/15 21:39:08/

前言

        本专栏面向所有希望或有兴趣从事数字图像处理工作、学习或研究的朋友,不懂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<stdlib.h>
#include<string.h>
#include<math.h>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;}elsereturn 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://bmpret = f_TImageSaveBmp(fileName, width, height, component, data);break;case 1://jpgret = f_TImageSaveJpg(fileName, width, height, component, data, 100);break;case 2://pngret = f_TImageSavePng(fileName, width, height, component, data, width * 4);break;case 3://tgaret = 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,有什么疑问欢迎相互讨论!

 

 


http://www.ppmy.cn/news/531929.html

相关文章

数字图像处理-数字水印的嵌入与提取

数字水印嵌入与提取 //本篇报告所有内容已上传csdn我的资源中&#xff08;包括源码与报告word和ppt&#xff09;&#xff0c;欢迎大家下载进行进一步修改研究。摘要&#xff1a;本报告重点介绍数字水印的嵌入与提取算法。数字水印根据水印的嵌入技术不同分为空间域数字水印和变…

数字图像处理——高斯低通滤波器(GLPF)实验报告

一、程序代码及注释 1、myGLPF函数 %pho参数为输入图像的路径 %D0参数为截止频率 function im2 myGLPF(pho,D0) ima imread(pho); %读取输入图像 %得到高斯低通滤波器 [r,c] size(ima); %获取输入图像的行和列 D zeros(r,c); %D(u,v)是距频率矩形中心的距离 for i1…

使用pytesseract进行图像识别字母和数字 (python3.x)

前言 策划说&#xff0c;有一些玩家会在头像上面p自己的微信号或者QQ号之类的&#xff0c;可不可以把他们自动筛选出来&#xff0c;于是找了资料研究了下&#xff0c;这里分享一下 先看运行效果 环境准备&#xff1a; (1) python版本3.x (2) 安装插件&#xff0c;pip3 insta…

数字图像处理(2): 颜色空间/模型—— RGB, CMY/CMYK, HSI, HSV, YUV

目录 1 什么是颜色 2 颜色的数字化 3 常见的颜色模型 3.1 RGB 模型 3.2 CMY/CMYK 模型 3.3 HSI 模型 3.4 HSV 模型 3.5 HSB 模型 3.6 Lab 模型 3.7 YUV 模型 3.8 模型分类 4 Python代码示例 参考资料 1 什么是颜色 颜色是通过眼、脑和我们的生活经验所产生的对光的…

数字图像处理--前言

文章结构 1. 基本概念2. 关于视觉感知3. 数字图像的表示 图像处理系列笔记&#xff1a; https://blog.csdn.net/qq_33208851/article/details/95335809 1. 基本概念 图像是一个二维亮度函数f(x,y),&#xff08;x,y)定义了空间坐标&#xff0c;f&#xff08;x,y)该点的亮度或灰…

数字图像处理--放大和收缩数字图像(双线性插值法)

数字图像处理--放大和收缩数字图像&#xff08;双线性插值法&#xff09; 前言放大和收缩数字图像步骤计算新的像素在原图的对应位置为这些对应位置赋灰度值 最近邻法赋灰度值双线性插值法赋灰度值一维线性插值双线性插值 代码最近邻法双线性插值 实验结果及对比分析后记 前言 …

基于混沌的变换域图像数字水印算法及其测试

一、项目需求分析 1.1项目介绍 本算法是将数字水印置乱加密后微小的扰动原始彩色图像经离散余弦变换后的对应系数,从而达到数字水印嵌入的目的。 实验表明算法简单高效,原图像与嵌入水印后图像差异小,水印提取准确,能较好的保证数字水印不可感知性,在对嵌入水印图像进行各种加…

数字图像处理基础(matlab)Ⅰ:图像读取,显示等

文章目录 前言一、实验目的二、实现原理三、算法设计3.1利用imread( )函数读取图像3.2 分别显示图像的红、绿和蓝色分量的强度3.3 将图像转换为灰度图&#xff0c;显示灰度结果图像进行对比3.4根据图像特征设计灰度变化曲线&#xff0c;实现灰度拉伸算法&#xff0c;显示拉伸前…