商业网站页面,公司企业网站制作教程,做招聘网站的怎么引流求职者,玉树营销网站建设Part11. 图像的属性 11.1 Mat 的主要属性 在前文中#xff0c;我们大致了解了 Mat 的基本结构以及它的创建与赋值。接下来我们通过一个例子#xff0c;来看看 Mat 所包含的常用属性。 先创建一个 3*4 的四通道的矩阵#xff0c;并打印出其相关的属性#xff0c;稍后会详细… Part11. 图像的属性 11.1 Mat 的主要属性 在前文中我们大致了解了 Mat 的基本结构以及它的创建与赋值。接下来我们通过一个例子来看看 Mat 所包含的常用属性。 先创建一个 3*4 的四通道的矩阵并打印出其相关的属性稍后会详细解释每个属性的含义。 Mat srcImage(3, 4, CV_16UC4, Scalar_uchar(1, 2, 3, 4));cout srcImage endl;cout dims: srcImage.dims endl;
cout rows: srcImage.rows endl;
cout cols: srcImage.cols endl;
cout channels: srcImage.channels() endl;
cout type: srcImage.type() endl;
cout depth: srcImage.depth() endl;
cout elemSize: srcImage.elemSize() endl;
cout elemSize1: srcImage.elemSize1() endl;
cout step: srcImage.step endl;
cout step[0]: srcImage.step[0] endl;
cout step[1]: srcImage.step[1] endl;
cout step1[0]: srcImage.step1(0) endl;
cout step1[1]: srcImage.step1(1) endl; 输出结果 [1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4;1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4;1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4]
dims:2
rows:3
cols:4
channels:4
type:26
depth:2
elemSize:8
elemSize1:2
step:32
step[0]:32
step[1]:8
step1[0]:16
step1[1]:4 在上述例子中我们打印了 Mat 的很多属性它们主要包括 rows 表示图像的高度。cols表示图像的宽度。dims表示矩阵的维度。data表示 Mat 对象中的指针uchar 类型的指针指向内存中存放矩阵数据的一块内存 uchar* data。channels表示通道数量例如常见的 RGB、HSV 彩色图像则 channels3若为灰度图则 channels1。depth表示图像的深度它用来度量每一个像素中每一个通道的精度它本身与通道数无关它的数值越大表示精度越高。 数据类型depth 的值数据类型取值范围对应 C 的类型CV_8U08 位无符号类型0—255uchar, unsigned charCV_8S18 位有符号类型-128—127charCV_16U216 位无符号类型0—65535ushort, unsigned short, unsigned short intCV_16S316 位有符号类型-32768—32767short, short intCV_32S432 位整数数据类型-2147483648—2147483647int, longCV_32F532 位浮点数类型±(1.18e-38……3.40e38)floatCV_64F632 位双精度类型±(2.23e-308……1.79e308)double type表示矩阵的数据类型它包含矩阵中元素的类型以及通道数信息。 数据类型1234CV_8UCV_8UC1CV_8UC2CV_8UC3CV_8UC4CV_8SCV_8SC1CV_8SC2CV_8SC3CV_8SC4CV_16UCV_16UC1CV_16UC2CV_16UC3CV_16UC4CV_16SCV_16SC1CV_16SC2CV_16SC3CV_16SC4CV_32SCV_32SC1CV_32SC2CV_32SC3CV_32SC4CV_32FCV_32FC1CV_32FC2CV_32FC3CV_32FC4CV_64FCV_64FC1CV_64FC2CV_64FC3CV_64FC4 elemSize表示矩阵中每一个元素的数据大小它与通道数相关单位是字节。 举几个例子 如果 Mat 中的数据类型是 CV_8UC1 或 CV_8SC1那么 elemSize1(1 * 8 / 8 1 bytes); 如果 Mat 中的数据类型是 CV_8UC3 或 CV_8SC3那么 elemSize3(3 * 8 / 8 3 bytes); 如果 Mat 中的数据类型是 CV_16UC3 或 CV_16SC3那么 elemSize6(3 * 16 / 8 6 bytes); 如果 Mat 中的数据类型是 CV_32SC3 或 CV_32FC3那么 elemSize12(3 * 32 / 8 12 bytes);elemSize1表示矩阵中每一个元素单个通道的数据大小单位是字节。满足 step 字面意思是“步长”实际上它描述了矩阵的形状。 step[] 为一个数组矩阵有几维step[] 数组就有几个元素。以一个三维矩阵为例step[0] 表示一个平面的字节总数step[1] 表示一行元素的字节总数step[2] 表示每一个元素的字节总数。 在 OpenCV 的官方文档中关于解释 step 时曾提到矩阵数据元素 的地址 对于我们常用的二维数组上述公式可化简为 这里的 step[0] 表示一行元素的字节总数step[1] 表示每一个元素的字节总数。 mat.png step1 step1 也是一个数组。step1 不再以字节为单位而是以 elemSize1 为单位满足 Part22. 图像的像素操作 22.1 像素的类型 我们最常用的图像是二维数组灰度图像(CV_8UC1)会存放 C 的 uchar 类型RGB 彩色图像一般会存放 Vec3b 类型。 其中单通道数据存放格式 三通道数据存放格式 对于彩色图像而言在 OpenCV 中通道的顺序是 B、G、R这跟我们通常所说的 RGB 三原色正好相反。 当然灰度图像也不一定都是 CV_8UC1 类型也可能是 CV_16SC1、CV_32FC1 等它们会存放 C 的 short、float 等基本类型。类似地彩色图像也可能是 CV_16SC3、CV_32FC3 等那它们是怎么存放的呢 OpenCV 定义了一系列的 Vec 类它是一个一维的向量代表像素的类型。 typedef Vecuchar, 2 Vec2b;
typedef Vecuchar, 3 Vec3b;
typedef Vecuchar, 4 Vec4b;typedef Vecshort, 2 Vec2s;
typedef Vecshort, 3 Vec3s;
typedef Vecshort, 4 Vec4s;typedef Vecushort, 2 Vec2w;
typedef Vecushort, 3 Vec3w;
typedef Vecushort, 4 Vec4w;typedef Vecint, 2 Vec2i;
typedef Vecint, 3 Vec3i;
typedef Vecint, 4 Vec4i;
typedef Vecint, 6 Vec6i;
typedef Vecint, 8 Vec8i;typedef Vecfloat, 2 Vec2f;
typedef Vecfloat, 3 Vec3f;
typedef Vecfloat, 4 Vec4f;
typedef Vecfloat, 6 Vec6f;typedef Vecdouble, 2 Vec2d;
typedef Vecdouble, 3 Vec3d;
typedef Vecdouble, 4 Vec4d;
typedef Vecdouble, 6 Vec6d; 其中 b、s、w、i、f、d 分别表示如下的含义 数据类型bunsigned charsshort intwunsigned shortiintffloatddouble Vec 类又被称为固定向量类在编译时就知道向量的大小。类似 Vec 这样的类还有Matx、Point、Size、Rect 我们用一张表总结一下矩阵中的数据类型和像素的类型的对应关系 数据类型C1C2C3C4C6CV_8UucharVec2bVec3bVec4bCV_8ScharVecchar, 2Vecchar, 3Vecchar, 4CV_16UushortVec2wVec3wVec4wCV_16SshortVec2sVec3sVec4sCV_32SintVec2iVec3iVec4iCV_32FfloatVec2fVec3fVec4fVec6fCV_64FdoubleVec2dVec3dVec4dVec6d 基于上述表格我们可以回答刚才的问题CV_16SC3 类型的图像存放的是 Vec3s 类型CV_32FC3 类型的图像存放的是 Vec3f 类型。 32.2 像素点的读取 Mat 的 at() 函数实现了对矩阵中的某个像素的读写操作。 下面的代码展示了 at() 函数对灰度图像像素的读写 Scalar value grayImage.atuchar(y, x);
Scalar.atuchar(y, x) 128; 三通道彩色的图像的读取 Vec3b value image.atVec3b(y, x);uchar blue value.val[0];
uchar green value.val[1];
uchar red value.val[2]; 三通道彩色图像的赋值 image.atVec3b(y,x)[0]128;
image.atVec3b(y,x)[1]128;
image.atVec3b(y,x)[2]128; 下面的例子结合像素的类型展示了将加载的图像转换成灰度图像以及对灰度图像进行取反的操作。 Mat srcImage imread(/Users/tony/beautiful.jpg);
if (srcImage.empty())
{cout could not load image ... endl;return -1;
}
imshow(src, srcImage);Mat grayImage;
cvtColor(srcImage, grayImage, COLOR_BGR2GRAY); // 灰度处理
imshow(gray,grayImage);int height grayImage.rows;
int width grayImage.cols;for (int row0; rowheight; row)
{for (int col0; colwidth; col){int gray grayImage.atuchar(row, col);grayImage.atuchar(row, col) 255- gray;}
}imshow(invert, grayImage); 像素点操作.png 简单提一下上述例子中 cvtColor() 函数的作用是将图像从一个颜色空间转换到另一个颜色空间。例如可以将图像从 BGR 色彩空间转换成灰度色彩空间或者从 BGR 色彩空间转换成 HSV 色彩空间等等。 42.3 图像的遍历 2.3.1 基于数组遍历 前面 2.2 介绍过 at() 函数可以对某个像素进行读写操作并用例子展示了对单通道进行遍历。 对于三通道的彩色图像可以这样遍历。 for(int i0;isrcImage.rows;i){for(int j0;jsrcImage.cols;j){srcImage.atVec3b(i,j)[0]... //B通道srcImage.atVec3b(i,j)[1]... //G通道srcImage.atVec3b(i,j)[2]... //R通道}
} 2.3.2 基于指针遍历 Mat 类提供了更高效的 ptr() 函数它可以得到图像任意行首地址。 下面的代码它返回第 i1 行的首地址也就是指向第 i1 行第一个元素的指针。 uchar* data srcImage.ptruchar(i); at() 函数跟 ptr() 函数在使用上有一定的区别 at类型(ij) ptr类型(i) 当然使用 ptr() 函数访问某个像素也是可以的采用如下的方式 mat.ptrtype(row)[col] 它返回的是 中的模板类型指针指向的是第 row1 行 col1 列的元素。 对于单通道图像的遍历 for(int i0;isrcImage.rows;i){uchar* datasrcImage.ptruchar(i);for(int j0;jsrcImage.cols;j){data[j]...}
} 对于三通道图像的遍历 for(int i0;isrcImage.rows;i){Vec3b* datasrcImage.ptrVec3b(i);for(int j0;jsrcImage.cols;j){data[j][0]... //B通道data[j][1]... //G通道data[j][2]... //R通道}
} 2.3.3 基于迭代器遍历 C STL 对每个集合类都定义了对应的迭代器类OpenCV 也提供了 cv::Mat 的迭代器类并且与 C STL 中的标准迭代器兼容。 对于单通道图像的遍历 Mat_uchar::iterator begin srcImage.beginuchar();
Mat_uchar::iterator end srcImage.enduchar();for (auto it begin; it ! end; it)
{*it ...
} 迭代器 Mat_ 是 Mat 的模版子类它重载了 operator() 让我们可以更方便的取图像上的点。类似的迭代器还有 Matlterator_。 对于三通道图像的遍历 Mat_cv::Vec3b::iterator begin srcImage.begincv::Vec3b();
Mat_cv::Vec3b::iterator end srcImage.endcv::Vec3b();for (auto it begin; it ! end; it)
{(*it)[0] ... //B通道(*it)[1] ... //G通道(*it)[2] ... //R通道
} 使用迭代器遍历图像会便捷一些但是效率没有使用指针的效率高。 52.3.4 基于 LUT 遍历 LUT (LOOK -UP-TABLE) 意为查找表。 在数据结构中查找表是由同一类型的 数据元素 构成的集合它是一种以查找为“核心”同时包括其他运算的非常灵活的数据结构。 在图像处理中经常会通过事先建立一张查找表对图像进行映射。 例如将灰度图由某个区间映射到另一个区间或者将单通道映射到三通道。它们都是以像素灰度值作为索引以灰度值映射后的数值作为表中的内容通过索引号与映射后的输出值建立联系。 一般灰度图像会有 0-255 个灰度值有时我们不需要这么精确的灰度级例如黑白图像。下面我们来展示如何建立一个 LUT将 64 到 196 之间的灰度值变成 0其余变成 1。 Mat lut(1, 256, CV_8U);
for (int i 0; i 256; i)
{if (i 64 and i 196){lut.atuchar(i) 0;}else{lut.atuchar(i) i;}
} 从上述代码可以看出通过改变图像中像素的灰度值LUT 可以降低灰度级提高运算速度。 LUT 只适用于 CV_8U 类型的图像。 当然查找表并不一定都是单通道的。 如果输入图像为单通道那么查找表为单通道。如果输入图像为三通道那么查找表可以为单通道或者三通道。 使用 LUT 进行遍历采用的是颜色空间缩减的方式把 unsigned char 类型的值除以一个 int 类型的值得到仍然是一个 char 类型的数值。 我们采用如下的公式 其中Q 表示量化级别当 Q 10 时则灰度值 1-10 用灰度值 1 表示灰度值 11-20 用灰度值 11 表示以此类推。256 个灰度值的灰度图像可以用 26 个数值表示那么彩色的图像就可以用 26 * 26 * 26 个数值表示比原先小了很多。 #include iostream
#include opencv2/core.hpp
#include opencv2/highgui.hppusing namespace std;
using namespace cv;#define QUAN_VAL1 10
#define QUAN_VAL2 20
#define QUAN_VAL3 100void createLookupTable(Mat table, uchar quanVal)
{table.create(1,256,CV_8UC1);uchar *p table.data;for(int i 0; i 256; i){p[i] quanVal*(i/quanVal); // 颜色缩减运算}
}int main()
{Mat srcImage imread(/Users/tony/beautiful.jpg);if (srcImage.empty()){cout could not load image ... endl;return -1;}imshow(src, srcImage); // 原图Mat table,dst1,dst2,dst3;createLookupTable(table, QUAN_VAL1);LUT(srcImage, table, dst1);createLookupTable(table, QUAN_VAL2);LUT(srcImage, table, dst2);createLookupTable(table, QUAN_VAL3);LUT(srcImage, table, dst3);imshow(dst1, dst1); // Q10imshow(dst2, dst2); // Q20imshow(dst3, dst3); // Q100waitKey(0);return 0;
} lut.png 上述例子在创建查找表时遍历了矩阵的每一个像素以及运用颜色空间缩减的运算公式。并且分别展示了原图、Q10、Q20、Q100 的图片。可以看到当 Q 100 时图像压缩得比较厉害丢失了很多信息。 Part33. 图像像素值的统计 63.1 均值与标准差 均值和标准差是统计学的概念。 均值的公式 标准差公式 在图像处理中它们能帮助我们了解图像通道中像素值的分布情况。均值表示图像整体的亮暗程度图像的均值越大则表示图像越亮。标准差表示图像中明暗变化的对比程度标准差越大表示图像中明暗变化越明显。 在图像分析的时候我们通过图像像素值的统计可以对图像的有效信息作出判断。当标准差很小时图像所携带的有效信息会很少便于我们判断这是否是我们所需要的图像。说一个题外话曾经我看到过一段很震惊的代码某同事写的判断传送带上手机是否亮屏。当时的代码可能是为了偷懒只通过判断图像的均值当均值超过某个阈值时就认为手机是亮屏的。后来我接手后当即做了大量的修改。 下面举个例子通过 meanStdDev() 函数获取图像的均值和标准差以及每个通道的均值和标准差。 Mat srcImage imread(/Users/tony/beautiful.jpg);
if (srcImage.empty())
{cout could not load image ... endl;return -1;
}
imshow(src, srcImage);Mat mean, stddev;
meanStdDev(srcImage, mean, stddev);
std::cout mean: std::endl mean std::endl;
std::cout stddev: std::endl stddev std::endl;
printf(blue channel mean:%.2f, stddev: %.2f \n, mean.atdouble(0, 0), stddev.atdouble(0, 0));
printf(green channel mean:%.2f, stddev: %.2f \n, mean.atdouble(1, 0), stddev.atdouble(1, 0));
printf(red channel mean:%.2f, stddev: %.2f \n, mean.atdouble(2, 0), stddev.atdouble(2, 0)); 输出结果 mean:
[91.28189117330051;104.7030620995939;118.9715339648672]
stddev:
[77.24017058254671;79.5424883584348;83.89088339080149]
blue channel mean:91.28, stddev: 77.24
green channel mean:104.70, stddev: 79.54
red channel mean:118.97, stddev: 83.89 Part44. 总结 本文过一个简单的例子介绍了 Mat 经常使用的属性和方法。后续还介绍了像素的类型和多种图像遍历的方式、像素值的统计。 在几种图像遍历方式中除了 LUT 遍历外其他的几种方式它们的效率从高到低依次为指针 迭代器 数组。在实际生产环境中我们经常会用指针遍历的方式。 本文介绍的内容是对前面一篇文章内容的补充它们都是 OpenCV 最基础的内容接下来的文章会经常使用这些内容。本文还引申出了 LUT 以及图像像素值的统计 特别是均值和标准差它们在图像预处理中经常用到。