• OpenCV 笔记(2):图像的属性以及像素相关的操作


    Part11.  图像的属性

    11.1 Mat 的主要属性

    在前文中,我们大致了解了 Mat 的基本结构以及它的创建与赋值。接下来我们通过一个例子,来看看 Mat 所包含的常用属性。

    先创建一个 3*4 的四通道的矩阵,并打印出其相关的属性,稍后会详细解释每个属性的含义。

    1. Mat srcImage(34, CV_16UC4, Scalar_(1234));
    2. cout << srcImage << endl;
    3. cout << "dims:" << srcImage.dims << endl;
    4. cout << "rows:" << srcImage.rows << endl;
    5. cout << "cols:" << srcImage.cols << endl;
    6. cout << "channels:" << srcImage.channels() << endl;
    7. cout << "type:" << srcImage.type() << endl;
    8. cout << "depth:" << srcImage.depth() << endl;
    9. cout << "elemSize:" << srcImage.elemSize() << endl;
    10. cout << "elemSize1:" << srcImage.elemSize1() << endl;
    11. cout << "step:" << srcImage.step << endl;
    12. cout << "step[0]:" << srcImage.step[0] << endl;
    13. cout << "step[1]:" << srcImage.step[1] << endl;
    14. cout << "step1[0]:" << srcImage.step1(0) << endl;
    15. cout << "step1[1]:" << srcImage.step1(1) << endl;

    输出结果:

    1. [1234123412341234;
    2.  1234123412341234;
    3.  1234123412341234]
    4. dims:2
    5. rows:3
    6. cols:4
    7. channels:4
    8. type:26
    9. depth:2
    10. elemSize:8
    11. elemSize1:2
    12. step:32
    13. step[0]:32
    14. step[1]:8
    15. step1[0]:16
    16. step1[1]:4

    在上述例子中我们打印了 Mat 的很多属性,它们主要包括:

    • rows: 表示图像的高度。

    • cols:表示图像的宽度。

    • dims:表示矩阵的维度。

    • data:表示 Mat 对象中的指针(uchar 类型的指针),指向内存中存放矩阵数据的一块内存 (uchar* data)。

    • channels:表示通道数量;例如常见的 RGB、HSV 彩色图像,则 channels=3;若为灰度图,则 channels=1。

    • depth:表示图像的深度,它用来度量每一个像素中每一个通道的精度,它本身与通道数无关,它的数值越大表示精度越高。

    数据类型depth 的值数据类型取值范围对应 C++ 的类型
    CV_8U08 位无符号类型0—255uchar,  unsigned char
    CV_8S18 位有符号类型-128—127char
    CV_16U216 位无符号类型0—65535ushort, unsigned short, unsigned short int
    CV_16S316 位有符号类型-32768—32767short, short int
    CV_32S432 位整数数据类型-2147483648—2147483647int, long
    CV_32F532 位浮点数类型±(1.18e-38……3.40e38)float
    CV_64F632 位双精度类型±(2.23e-308……1.79e308)double
    • type:表示矩阵的数据类型,它包含矩阵中元素的类型以及通道数信息。

    数据类型1234
    CV_8UCV_8UC1CV_8UC2CV_8UC3CV_8UC4
    CV_8SCV_8SC1CV_8SC2CV_8SC3CV_8SC4
    CV_16UCV_16UC1CV_16UC2CV_16UC3CV_16UC4
    CV_16SCV_16SC1CV_16SC2CV_16SC3CV_16SC4
    CV_32SCV_32SC1CV_32SC2CV_32SC3CV_32SC4
    CV_32FCV_32FC1CV_32FC2CV_32FC3CV_32FC4
    CV_64FCV_64FC1CV_64FC2CV_64FC3CV_64FC4
    • elemSize:表示矩阵中每一个元素的数据大小,它与通道数相关,单位是字节。 举几个例子: 如果 Mat 中的数据类型是 CV_8UC1 或 CV_8SC1,那么 elemSize=1(1 * 8 / 8 = 1 bytes); 如果 Mat 中的数据类型是 CV_8UC3 或 CV_8SC3,那么 elemSize=3(3 * 8 / 8 = 3 bytes); 如果 Mat 中的数据类型是 CV_16UC3 或 CV_16SC3,那么 elemSize=6(3 * 16 / 8 = 6 bytes); 如果 Mat 中的数据类型是 CV_32SC3 或 CV_32FC3,那么 elemSize=12(3 * 32 / 8 = 12 bytes);

    • elemSize1:表示矩阵中每一个元素单个通道的数据大小,单位是字节。满足:

    • step: 字面意思是“步长”,实际上它描述了矩阵的形状。 step[] 为一个数组,矩阵有几维,step[] 数组就有几个元素。以一个三维矩阵为例,step[0] 表示一个平面的字节总数,step[1] 表示一行元素的字节总数,step[2] 表示每一个元素的字节总数。

    在 OpenCV 的官方文档中,关于解释 step 时曾提到矩阵数据元素

    的地址

    对于我们常用的二维数组,上述公式可化简为:

    这里的 step[0] 表示一行元素的字节总数,step[1] 表示每一个元素的字节总数。

    3bf654ccdd7b6cd66fc72bb264f5272c.jpeg
    mat.png
    • step1:  step1 也是一个数组。step1 不再以字节为单位,而是以 elemSize1 为单位,满足:

    Part22. 图像的像素操作

    22.1 像素的类型

    我们最常用的图像是二维数组,灰度图像(CV_8UC1)会存放 C++ 的 uchar 类型,RGB 彩色图像一般会存放 Vec3b 类型。

    其中,单通道数据存放格式:1da9387b2563892f09475de4b8d1b482.jpeg

    三通道数据存放格式:6b8c558c0b29fac2c5d9ead59bbc57bf.jpeg

    对于彩色图像而言,在 OpenCV 中通道的顺序是 B、G、R,这跟我们通常所说的 RGB 三原色正好相反。

    当然,灰度图像也不一定都是 CV_8UC1 类型,也可能是 CV_16SC1、CV_32FC1 等,它们会存放 C++ 的 short、float 等基本类型。类似地,彩色图像也可能是 CV_16SC3、CV_32FC3 等,那它们是怎么存放的呢?

    OpenCV 定义了一系列的 Vec 类,它是一个一维的向量,代表像素的类型

    1. typedef Vec2> Vec2b;
    2. typedef Vec3> Vec3b;
    3. typedef Vec4> Vec4b;
    4. typedef Vec2> Vec2s;
    5. typedef Vec3> Vec3s;
    6. typedef Vec4> Vec4s;
    7. typedef Vec2> Vec2w;
    8. typedef Vec3> Vec3w;
    9. typedef Vec4> Vec4w;
    10. typedef Vec<int2> Vec2i;
    11. typedef Vec<int3> Vec3i;
    12. typedef Vec<int4> Vec4i;
    13. typedef Vec<int6> Vec6i;
    14. typedef Vec<int8> Vec8i;
    15. typedef Vec2> Vec2f;
    16. typedef Vec3> Vec3f;
    17. typedef Vec4> Vec4f;
    18. typedef Vec6> Vec6f;
    19. typedef Vec2> Vec2d;
    20. typedef Vec3> Vec3d;
    21. typedef Vec4> Vec4d;
    22. typedef Vec6> Vec6d;

    其中 b、s、w、i、f、d 分别表示如下的含义:


    数据类型
    bunsigned char
    sshort int
    wunsigned short
    iint
    ffloat
    ddouble

    Vec 类又被称为固定向量类,在编译时就知道向量的大小。类似 Vec 这样的类还有:Matx、Point、Size、Rect

    我们用一张表,总结一下矩阵中的数据类型和像素的类型的对应关系:

    数据类型C1C2C3C4C6
    CV_8UucharVec2bVec3bVec4b
    CV_8ScharVecVecVec
    CV_16UushortVec2wVec3wVec4w
    CV_16SshortVec2sVec3sVec4s
    CV_32SintVec2iVec3iVec4i
    CV_32FfloatVec2fVec3fVec4fVec6f
    CV_64FdoubleVec2dVec3dVec4dVec6d

    基于上述表格我们可以回答刚才的问题,CV_16SC3 类型的图像存放的是 Vec3s 类型,CV_32FC3 类型的图像存放的是 Vec3f 类型。

    32.2 像素点的读取

    Mat 的 at() 函数实现了对矩阵中的某个像素的读写操作

    下面的代码展示了 at() 函数对灰度图像像素的读写:

    1. Scalar value = grayImage.at(y, x);
    2. Scalar.at(y, x) = 128;

    三通道彩色的图像的读取:

    1. Vec3b value = image.at(y, x);
    2. uchar blue = value.val[0];
    3. uchar green = value.val[1];
    4. uchar red = value.val[2];

    三通道彩色图像的赋值:

    1. image.at(y,x)[0]=128;
    2. image.at(y,x)[1]=128;
    3. image.at(y,x)[2]=128;

    下面的例子结合像素的类型,展示了将加载的图像转换成灰度图像,以及对灰度图像进行取反的操作。

    1. Mat srcImage = imread("/Users/tony/beautiful.jpg");
    2. if (srcImage.empty())
    3. {
    4.     cout << "could not load image ..." << endl;
    5.     return -1;
    6. }
    7. imshow("src", srcImage);
    8. Mat grayImage;
    9. cvtColor(srcImage, grayImage, COLOR_BGR2GRAY); // 灰度处理
    10. imshow("gray",grayImage);
    11. int height = grayImage.rows;
    12. int width  = grayImage.cols;
    13. for (int row=0; row
    14. {
    15.     for (int col=0; col
    16.     {
    17.         int gray = grayImage.at(row, col);
    18.         grayImage.at(row, col) = 255- gray;
    19.     }
    20. }
    21. imshow("invert", grayImage);
    a215bdd16c6d5c8cffbb10d511bd67ab.jpeg
    像素点操作.png

    简单提一下,上述例子中 cvtColor() 函数的作用是将图像从一个颜色空间转换到另一个颜色空间。例如,可以将图像从 BGR 色彩空间转换成灰度色彩空间,或者从 BGR 色彩空间转换成 HSV 色彩空间等等。

    42.3 图像的遍历

    2.3.1 基于数组遍历

    前面 2.2 介绍过 at() 函数可以对某个像素进行读写操作,并用例子展示了对单通道进行遍历。

    对于三通道的彩色图像可以这样遍历。

    1. for(int i=0;i
    2.     for(int j=0;j
    3.         srcImage.at(i,j)[0]=...  //B通道
    4.         srcImage.at(i,j)[1]=...  //G通道
    5.         srcImage.at(i,j)[2]=...  //R通道
    6.     }
    7. }

    2.3.2 基于指针遍历

    Mat 类提供了更高效的 ptr() 函数,它可以得到图像任意行首地址

    下面的代码,它返回第 i+1 行的首地址,也就是指向第 i+1 行第一个元素的指针。

    uchar* data = srcImage.ptr(i);

    at() 函数跟 ptr() 函数在使用上有一定的区别:

    at<类型>(i,j) 

    ptr<类型>(i)

    当然,使用 ptr()  函数访问某个像素也是可以的,采用如下的方式:

    mat.ptr<type>(row)[col]

    它返回的是 <> 中的模板类型指针,指向的是第 row+1 行 col+1 列的元素。

    对于单通道图像的遍历:

    1. for(int i=0;i
    2.     uchar* data=srcImage.ptr(i);
    3.     for(int j=0;j
    4.         data[j]=...
    5.     }
    6. }

    对于三通道图像的遍历:

    1. for(int i=0;i
    2.     Vec3b* data=srcImage.ptr(i);
    3.     for(int j=0;j
    4.         data[j][0]=...  //B通道
    5.         data[j][1]=...  //G通道
    6.         data[j][2]=...  //R通道
    7.     }
    8. }

    2.3.3 基于迭代器遍历

    C++ STL 对每个集合类都定义了对应的迭代器类,OpenCV 也提供了 cv::Mat 的迭代器类,并且与 C++ STL 中的标准迭代器兼容。

    对于单通道图像的遍历:

    1. Mat_::iterator begin = srcImage.begin();
    2. Mat_::iterator end = srcImage.end();
    3. for (auto it = begin; it != end; it++)
    4. {
    5.     *it = ...
    6. }

    迭代器 Mat_ 是 Mat 的模版子类,它重载了 operator() 让我们可以更方便的取图像上的点。类似的迭代器还有 Matlterator_。

    对于三通道图像的遍历:

    1. Mat_::iterator begin = srcImage.begin();
    2. Mat_::iterator end = srcImage.end();
    3. for (auto it = begin; it != end; it++)
    4. {
    5.     (*it)[0] = ... //B通道
    6.     (*it)[1] = ... //G通道
    7.     (*it)[2] = ... //R通道
    8. }

    使用迭代器遍历图像会便捷一些,但是效率没有使用指针的效率高。

    52.3.4 基于 LUT 遍历

    LUT (LOOK -UP-TABLE) 意为查找表。

    在数据结构中,查找表是由同一类型的 数据元素 构成的集合,它是一种以查找为“核心”,同时包括其他运算的非常灵活的数据结构。

    在图像处理中,经常会通过事先建立一张查找表对图像进行映射。

    例如,将灰度图由某个区间映射到另一个区间,或者将单通道映射到三通道。它们都是以像素灰度值作为索引,以灰度值映射后的数值作为表中的内容,通过索引号与映射后的输出值建立联系。

    一般灰度图像会有 0-255 个灰度值,有时我们不需要这么精确的灰度级,例如黑白图像。下面我们来展示如何建立一个 LUT,将 64 到 196 之间的灰度值变成 0,其余变成 1。

    1. Mat lut(1256, CV_8U);
    2. for (int i = 0; i < 256; i++)
    3. {
    4.     if (i > 64 and i < 196)
    5.     {
    6.         lut.at(i) = 0;
    7.     }
    8.     else
    9.     {
    10.         lut.at(i) = i;
    11.     }
    12. }

    从上述代码可以看出,通过改变图像中像素的灰度值,LUT 可以降低灰度级提高运算速度。

    LUT 只适用于 CV_8U 类型的图像。

    当然,查找表并不一定都是单通道的。

    • 如果输入图像为单通道,那么查找表为单通道

    • 如果输入图像为三通道,那么查找表可以为单通道或者三通道

    使用 LUT 进行遍历,采用的是颜色空间缩减的方式:把 unsigned char 类型的值除以一个 int 类型的值,得到仍然是一个 char 类型的数值。

    我们采用如下的公式:

    其中,Q 表示量化级别,当 Q= 10 时则灰度值 1-10 用灰度值 1 表示,灰度值 11-20 用灰度值 11 表示,以此类推。256 个灰度值的灰度图像可以用 26 个数值表示,那么彩色的图像就可以用 26 * 26 * 26 个数值表示,比原先小了很多。

    1. #include 
    2. #include 
    3. #include 
    4. using namespace std;
    5. using namespace cv;
    6. #define QUAN_VAL1          10
    7. #define QUAN_VAL2          20
    8. #define QUAN_VAL3          100
    9. void createLookupTable(Mat& table, uchar quanVal)
    10. {
    11.     table.create(1,256,CV_8UC1);
    12.     uchar *p = table.data;
    13.     for(int i = 0; i < 256; ++i)
    14.     {
    15.         p[i] = quanVal*(i/quanVal); // 颜色缩减运算
    16.     }
    17. }
    18. int main()
    19. {
    20.     Mat srcImage = imread("/Users/tony/beautiful.jpg");
    21.     if (srcImage.empty())
    22.     {
    23.         cout << "could not load image ..." << endl;
    24.         return -1;
    25.     }
    26.     imshow("src", srcImage); // 原图
    27.     Mat table,dst1,dst2,dst3;
    28.     createLookupTable(table, QUAN_VAL1);
    29.     LUT(srcImage, table, dst1);
    30.     createLookupTable(table, QUAN_VAL2);
    31.     LUT(srcImage, table, dst2);
    32.     createLookupTable(table, QUAN_VAL3);
    33.     LUT(srcImage, table, dst3);
    34.     imshow("dst1", dst1); // Q=10
    35.     imshow("dst2", dst2); // Q=20
    36.     imshow("dst3", dst3); // Q=100
    37.     waitKey(0);
    38.     return 0;
    39. }
    0d50d123ee3fb7c6195e34a9a1b493c9.jpeg
    lut.png

    上述例子在创建查找表时,遍历了矩阵的每一个像素以及运用颜色空间缩减的运算公式。并且分别展示了原图、Q=10、Q=20、Q=100 的图片。可以看到当 Q = 100 时,图像压缩得比较厉害丢失了很多信息。

    Part33. 图像像素值的统计

    63.1 均值与标准差

    均值和标准差是统计学的概念。

    均值的公式:

    标准差公式:

    在图像处理中,它们能帮助我们了解图像通道中像素值的分布情况。均值表示图像整体的亮暗程度,图像的均值越大则表示图像越亮。标准差表示图像中明暗变化的对比程度,标准差越大表示图像中明暗变化越明显。

    在图像分析的时候,我们通过图像像素值的统计,可以对图像的有效信息作出判断。当标准差很小时,图像所携带的有效信息会很少,便于我们判断这是否是我们所需要的图像。说一个题外话,曾经我看到过一段很震惊的代码,某同事写的判断传送带上手机是否亮屏。当时的代码可能是为了偷懒,只通过判断图像的均值,当均值超过某个阈值时就认为手机是亮屏的。后来我接手后,当即做了大量的修改。

    下面举个例子,通过 meanStdDev() 函数获取图像的均值和标准差,以及每个通道的均值和标准差。

    1. Mat srcImage = imread("/Users/tony/beautiful.jpg");
    2. if (srcImage.empty())
    3. {
    4.     cout << "could not load image ..." << endl;
    5.     return -1;
    6. }
    7. imshow("src", srcImage);
    8. Mat mean, stddev;
    9. meanStdDev(srcImage, mean, stddev);
    10. std::cout << "mean:" << std::endl << mean << std::endl;
    11. std::cout << "stddev:" << std::endl<< stddev << std::endl;
    12. printf("blue channel mean:%.2f, stddev: %.2f \n", mean.at(00), stddev.at(00));
    13. printf("green channel mean:%.2f, stddev: %.2f \n", mean.at(10), stddev.at(10));
    14. printf("red channel mean:%.2f, stddev: %.2f \n", mean.at(20), stddev.at(20));

    输出结果:

    1. mean:
    2. [91.28189117330051;
    3.  104.7030620995939;
    4.  118.9715339648672]
    5. stddev:
    6. [77.24017058254671;
    7.  79.5424883584348;
    8.  83.89088339080149]
    9. blue channel mean:91.28, stddev: 77.24 
    10. green channel mean:104.70, stddev: 79.54 
    11. red channel mean:118.97, stddev: 83.89

    Part44. 总结

    本文过一个简单的例子,介绍了 Mat 经常使用的属性和方法。后续还介绍了像素的类型和多种图像遍历的方式、像素值的统计。

    在几种图像遍历方式中,除了 LUT 遍历外,其他的几种方式它们的效率从高到低依次为:指针 > 迭代器 > 数组。在实际生产环境中,我们经常会用指针遍历的方式

    本文介绍的内容是对前面一篇文章内容的补充,它们都是 OpenCV 最基础的内容,接下来的文章会经常使用这些内容。本文还引申出了 LUT 以及图像像素值的统计, 特别是均值和标准差它们在图像预处理中经常用到。

  • 相关阅读:
    【Redis入门笔记 01】redis 安装 & 配置
    网络编程进化史:Netty Channel 的崭新篇章
    高并发与秒杀实战
    Golang入门笔记(7)—— 函数 func
    MACOS降级
    【译】ASP.NET Core 6 中的性能改进
    Ubuntu18保姆级教程及其jdk和hadoop安装含资源
    JAVA虚拟机--JVM
    Vue技术-mapstart与mapGetters=》模块化+命名空间
    Swift报错:“‘nil‘ is incompatible with return type ‘User‘”
  • 原文地址:https://blog.csdn.net/SLFq6OF5O7aH/article/details/133937420