OpenCV 中基本数据结构 Mat、Vec、Scalar、Point、Size、Rect

opencv 常见的数据结构,包括标量、点、向量、区域、Mat

OpenCV 几种常见的基本数据结构?

  • Cv: : Vec 一个模板类,主要用于存储数值向量
  • Cv: : Scalar 从 Vec 类引出的模板类,是一个可存放 4 个元素的向量,广泛用于传递和读取图像中的像素值
  • Cv: : Point 表示 2 维坐标 (x, y)
  • Cv: : Size 表示一幅图像或一个矩形的大小。它包含宽、高 2 个成员:width , height,还有一个有用的面积函数 area ()
  • Cv: : Rect 用于定义 2 维矩形的模板类,包括:矩形左上角坐标: (x, y),矩形的宽和高: width, height
  • Cv: : RotatedRect 一种特殊的矩形称为 RotatedRect。这个类通过中心点,宽度和高度和旋转角度来表示一个旋转的矩形

什么是 OpenCV 的 Mat 类?

  • Mat 类是存储图像的数据结构,在 core 模块中定义
  • Mat 本质上是由两个数据部分组成的类: 矩阵头和一个指针,指向包含了像素值的矩阵。 矩阵头部的大小是恒定的。然而,矩阵本身的大小因图像的不同而不同,通常是较大的数量级
    1
    2
    3
    4
    Mat A, C; //仅创建了头部
    A = imread(argv[1], CV_LOAD_IMAGE_COLOR); //在此我们知道使用的方法(分配矩阵)
    Mat B(A); //使用拷贝构造函数
    C = A; //赋值运算符

OpenCV 的 Mat 类有哪些常见属性?

  • data uchar 型的指针,Mat 类分为了两个部分:矩阵头和指向矩阵数据部分的指针,data 就是指向矩阵数据的指针
  • dims 矩阵的维度,例如 5x6 矩阵是二维矩阵,则 dims=2,三维矩阵 dims=3.
  • rows 矩阵的行数
  • cols 矩阵的列数
  • size 矩阵的大小,size (cols,rows), 如果矩阵的维数大于 2,则是 size (-1,-1)
  • channels 矩阵元素拥有的通道数,例如常见的彩色图像,每一个像素由 RGB 三部分组成,则 channels = 3
  • OpenCV Mat 类的 type 表示了矩阵中元素的类型以及矩阵的通道个数,它是一系列的预定义的常量,其命名规则为 CV_(位数)+(数据类型)+(通道数)
  • depth 矩阵中元素的一个通道的数据类型,这个值和 type 是相关的, 将 type 的预定义值去掉通道信息就是 depth 值
  • elemSize 矩阵一个元素占用的字节数,例如:type 是 CV_16SC3,那么 elemSize = 3 * 16 / 8 = 6 bytes
  • elemSize1 矩阵元素一个通道占用的字节数,例如:type 是 CV_16CS3,那么 elemSize1 = 16 / 8 = 2 bytes = elemSize /channels

OpenCV 中 Mat 数据在内存的组织方式?

  • 单通道数据按排列顺序存储;多通道数据按通道顺序 - 排列顺序存储
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    // 2x2x3Mat
    cv::Mat listMat[3];
    cv::Mat channeMatl = (cv::Mat_(2, 2, 1) << 1.1, 2.1, 3.1, 4.1);
    cv::Mat channeMat2 = (cv::Mat_(2, 2, 1) << 1.2, 2.2, 3.2, 4.2);
    cv::Mat channeMat3 = (cv::Mat_(2, 2, 1) << 1.3, 2.3, 3.3, 4.3);
    listMat[0] = channeMatl.clone();
    listMat[1] = channeMat2.clone();
    listMat[2] = channeMat3.clone();

    // 多通道数据按像素位置打印
    cv::Mat mergeMat;
    cv::merge(listMat, 3, mergeMat);


    std::vector chann{ "通道1","通道2","通道3" };
    for (int c = 0; c < mergeMat.channels(); c++) {
    std::cout << "-----------"<(h, w)).val[c]) << ",";
    }
    std::cout << "" << std::endl;
    }
    std::cout << "---------------------------" << std::endl;
    }

    // 按存储顺序打印
    float* matData = (float*)mergeMat.data;
    for (int i = 0; i < mergeMat.cols * mergeMat.rows * mergeMat.channels(); i++) {
    std::cout << matData[i] << ",";
    }

    //-----------------输出-------------------------
    -----------通道1-----------
    1.1,2.1,
    3.1,4.1,
    ---------------------------
    -----------通道2-----------
    1.2,2.2,
    3.2,4.2,
    ---------------------------
    -----------通道3-----------
    1.3,2.3,
    3.3,4.3,
    ---------------------------
    1.1,1.2,1.3,2.1,2.2,2.3,3.1,3.2,3.3,4.1,4.2,4.3,

opencv 的 ones 生成 Mat 对象的注意事项?

  • ones 生成多通道的 Mat 对象时,只有第一个通道的数据为 1,其他通道为 0
    1
    2
    3
    cv::Mat m = cv::Mat::ones(2,2CV_8UC3); 
    # [1,0,0] [1,0,0]
    # [1,0,0] [1,0,0]

认识 OpenCV 的 Mat 类的 type 值?

  • Mat 的 type 命名规则为:CV_(位数)+(数据类型)+(通道数)

C++ 上 OpenCV 的 convertTo 函数?

  • 用于转换 Mat 的数据类型,需要提供 3 个参数:数据类型、alpha、beta,计算时先进行以下公式计算,然后再转换数据类型 $$DstMat(x,y)=SrcMat(x,y)\times \alpha + \beta$$
  • 转换前需要观察前者的数据范围,比如同样是 float 32 转 uint 8,如果其被归一化了,需要设置 alpha=255.0,否则使用默认值
    1
    2
    3
    4
    5
    6
    7
    // CV32F转CV8U
    cv::Mat matTemp = cv::Mat::zeros(100,100,CV_32F); //得到一个浮点型的100*100的矩阵
    cv::Mat MatTemp2;
    //把矩阵matTemp转为unsing char类型的矩阵,注在转换过程中有可能数值上会出现一些变化,这个要注意
    matTemp.convertTo(MatTemp2, CV_8U);
    //将[0,1]范围内的浮点表示的图像转换成8bit整数表示的图像
    float_image.convertTo(integer_image, CV_8U, 255.0);
  • 注意:这函数转换时,会进行四舍五入,假设 Python 使用 numpy 的 astype 转换数据类型,会发现和使用 C++ 使用 opencv 的 convertTo 差 1,将 astype 改为 round 即可

C++ 上 OpenCV 进行数据转换前遵循的原则?

  • 在从小的数据范围转向大的数据范围时,必须先改变类型
    1
    2
    3
    // CV_8U转为CV_16U
    mat.convertTo(mat,CV16U); //代表的是16U,注意,转类型的时候不需要考虑通道
    mat = mat * 257; //8bit的数据乘以257就转为了16bit
  • 整数类型转小数类型时,先转类型再转数据
    1
    2
    3
    4
    //先转为32F
    img.convertTo(img, CV32F);
    //再转为小数
    img = img / 65535;

C++ 上 OpenCV 的 Mat 数据与 byte 互转?

  • Mat 转 byte
    1
    2
    3
    4
    5
    6
    byte * matToBytes(Mat image)
    {
    int size = image.total() * image.elemSize();
    byte * bytes = new byte[size]; // you will have to delete[] that later
    std::memcpy(bytes,image.data,size * sizeof(byte));
    }
  • byte 转 Mat
    1
    2
    3
    4
    5
    Mat bytesToMat(byte * bytes,int width,int height)
    {
    Mat image = Mat(height,width,CV_8UC3,bytes).clone(); // make a copy
    return image;
    }

Python 上如何将 float 的 Mat 转 unit8 的 Mat?

  • float 转为 unit8,有可能会造成数据的损失,因此会有警告提醒
    1
    2
    3
    4
    5
    6
    from skimage import img_as_ubyte
    import numpy as np
    img = np.array([0, 0.5, 1], dtype=float)
    print(img.dtype.name)
    dst=img_as_ubyte(img)
    print(dst.dtype.name)

如何在 cv: :Mat 中找到最大值、最小值?

  • 方法 1: std 中 algorithm
    1
    2
    3
    4
    5
    6
    7
    8
    #include      // std::cout
    #include // std::min_element, std::max_element

    cv::Mat img = cv::imread("path-to-image/juice.tiff");

    // 假设图片数据类型位float
    float maxValue = *max_element(img.begin(), img.end());
    float minValue = *min_element(img.begin(), img.end());
  • 方法 2: cv 中 minMaxLoc
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    int main()
    {
    // std::cout << "Hello World!\n";
    cv::Mat image = cv::imread("path-to-image/juice.png");
    cv::Mat image_re = image.reshape(1);
    double minValue, maxValue; // 最大值,最小值
    cv::Point minIdx, maxIdx; // 最小值坐标,最大值坐标
    cv::minMaxLoc(image_re, &minValue, &maxValue, &minIdx, &maxIdx);
    std::cout << "最大值:" << maxValue <<"最小值:"<
    }
  • 方法 3: 遍历 Mat
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    int main(int argc, char* argv[])
    {
    float Tval = 0.0;
    float RawData[2][3] = {{4.0,1.0,3.0},{8.0,7.0,9.0}};
    Mat RawDataMat(2,3,CV_32FC1,RawData);
    double minv = 0.0, maxv = 0.0;
    double* minp = &minv;
    double* maxp = &maxv;
    minMaxIdx(RawDataMat,minp,maxp);
    cout << "Mat minv = " << minv << endl;
    cout << "Mat maxv = " << maxv << endl;
    return 0;
    }

访问 OpenCV 中 Mat 数据的方法?

  • 利用 at 访问 Mat 中的元素:at 方法后面跟 <数据类型>,此处数据类型需与矩阵定义时一致,否则报错
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    #  单通道
    std::cout<(nrow,ncol);
    std::cout<<(int)val<<",";
    }
    std::cout << std::endl;
    }
    # 3通道
    for( size_t nrow = 0; nrow < demoImageC3.rows; nrow++)
    {
    for(size_t ncol = 0; ncol < demoImageC3.cols; ncol++)
    {
    Vec3i bgr = demoImageC3.at(nrow,ncol);//用Vec3b也行
    }
    }
    • 使用指针读取
      1
      2
      3
      4
      5
      6
      7
      for(size_t nrow=0;nrow<demoImageC3.rows;nrow++){
      uchar* data=demoImageC3.ptr<uchar>(nrow);
      for(size_t ncol=0;ncol<demoImageC3.cols*demoImageC3.channels();ncol++){
      std::cout<<int(data[ncol])<<",";
      }
      std::cout<<std::endl;
      }
  • 使用迭代器
    1
    2
    3
    4
    5
    6
    7
    8
    MatIterator_<uchar> it=demoImageC3.begin<uchar>();
    MatIterator_<uchar> it_end=demoImageC3.end<uchar>();
    for(int cnt=1;it!=it_end;++it){
    std::cout<<(int(*it))<<",";
    if((cnt++%demoImageC3.cols)==0){
    std::cout<<std::endl;
    }
    }
  • 使用矩阵地址
    1
    2
    3
    4
    5
    6
    for(size_t nrow=0;nrow<demoImageC3.rows;nrow++){
    for(size_t ncol=0;ncol<demoImageC3.cols;ncol++){
    std::cout<<(int)(*(demoImageC3.data+demoImageC3.step[0]*nrow+demoImageC3.step[1]*ncol))<<",";
    }
    std::cout<<std::endl;
    }

如何快速扫描 Mat,并进行值替换?

  • 在图像处理中,很常见的是要将所有给定的图像值修改为其他值。OpenCV 提供了修改图像值的功能,无需编写图像的扫描逻辑。
  • 使用 core 模块的 cv: :LUT 函数完成快速转换

如何将一个 CV32F 的 Mat 保存到文本文件中?

  • 直接遍历存储
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    ofstream fileWrite("afternormalizeData.txt");
    for (size_t nrow = 0; nrow < outputMat.rows; nrow++)
    {
    for (size_t ncol = 0; ncol < outputMat.cols; ncol++)
    {
    // 使用cv::Vec3f读取
    cv::Vec3f bgr = outputMat.at(nrow, ncol);
    // 默认输出某一行
    if (nrow == 250) {
    string saveString = std::to_string(nrow) + "-" + std::to_string(ncol) + ":" + (" + std::to_string(bgr.val[0]) + "," + std::to_string(bgr.val[1]) + "," + std::to_string(bgr.val[2]) + ")" + "\n";
    fileWrite.write(saveString.c_str(), saveString.size());
    }
    }
    }
    fileWrite.close();

如何将 Mat 类转为数组 Vector?

  • Mat 类为 uchar
    1
    2
    3
    4
    5
    6
    7
    8
    9
    std::vector array;
    if (mat.isContinuous()) {
    // array.assign(mat.datastart, mat.dataend); // <- has problems for sub-matrix like mat = big_mat.row(i)
    array.assign(mat.data, mat.data + mat.total());
    } else {
    for (int i = 0; i < mat.rows; ++i) {
    array.insert(array.end(), mat.ptr(i), mat.ptr(i)+mat.cols);
    }
    }
  • Mat 类为 float
    1
    2
    3
    4
    5
    6
    7
    8
    9
    std::vector array;
    if (mat.isContinuous()) {
    // array.assign((float*)mat.datastart, (float*)mat.dataend); // <- has problems for sub-matrix like mat = big_mat.row(i)
    array.assign((float*)mat.data, (float*)mat.data + mat.total());
    } else {
    for (int i = 0; i < mat.rows; ++i) {
    array.insert(array.end(), mat.ptr(i), mat.ptr(i)+mat.cols);
    }
    }

如何分通道处理 Mat 中的数据?

  • for 循环遍历
    1
    2
    3
    4
    5
    6
    7
    8
    9
    std::vector  meanValues= { 0.4,0.3,0.2 };
    std::vector stdValues = { 0.2,0.3,0.4 };
    for (int c = 0; c < inputMat.channels(); c++) {
    for (int n = 0; n < inputMat.size().height; n++) {
    for (int m = 0; m < inputMat.size().width; m++) {
    cv::Vec3i(outputMat.at(n, m)).val[c] = (float(cv::Vec3f(inputMat.at(n, m)).val[c]) - meanValues.at(c)) / stdValues.at(c);
    }
    }
    }
  • 拆分通道后处理
    1
    2
    3
    4
    5
    6
    7
    // 处理更快
    cv::Mat splitMat[3],outputMat;
    cv::split(inputMat, splitMat);
    for (int c = 0; c < inputMat.channels(); c++) {
    splitMat[c] = (splitMat[c] - meanValues.at(c)) / stdValues.at(c);
    }
    cv::merge(splitMat, 3, outputMat);

OpenCV 如何定义 ROI 区域?

  • 第一种,使用 cv: :Rect
    1
    2
    Mat imageROI;
    imageROI=image(Rect(500,250,logo.cols,logo.rows));
  • 第二种,使用 cv: :Range
    1
    imageROI=image(Range(250,250+logoImage.rows),Range(200,200+logoImage.cols));