CV02-01:OpenCV之Mat与图像表示

2019-11-12  本文已影响0人  杨强AT南京

  Mat是OpenCV中表示图像最基本的数据结构,本主题主要介绍这个类的使用。同时本主题使用的是C++11的语法标准,因篇幅缘故,所以对Mat相关的内容没有进一步展开,有兴趣可以参考OpenCV的官方文档。


构造器的使用

基本构造器使用

  1. 构造器定义

cv::Mat::Mat(   
    int rows,
    int cols,
    int type,
    void * data,
    size_t step=AUTO_STEP 
)
  1. 参数说明

    1. int rows:矩阵的行数(如果是图像,表示像素个数);
    2. int cols:矩阵的列数
    3. int type:矩阵元素的数据类型,数据类型是使用宏预定义的:
      • 宏定义可以使用通道,通道数1-4(用来对应颜色的RGBA),具体的宏的定义见下面的说明。
    4. void * data:用来填充矩阵的数据内存(使用指针指向数据缓冲的地址)
    5. size_t step=AUTO_STEP:矩阵行的字节数(默认AUTO_STEP就是元素类型长度*cols),单位:字节bytes
      • 默认参数AUTO_STEP的计算方式:cols*elemSize():简易采用默认参数。
      • AUTO_STEP是枚举类型,表示0。
  2. 矩阵数据类型的宏:

    1. 基本类型:
      • #define CV_8U 0
      • #define CV_8S 1
      • #define CV_16U 2
      • #define CV_16S 3
      • #define CV_32S 4
      • #define CV_32F 5
      • #define CV_64F 6
      • #define CV_16F 7
    2. 带通道的宏
      • 格式:类型Cn,其中的n表示1,2,3,4,例子:CV_8UC1CV_8UC2CV_8UC3CV_8UC4
      • 这样8个基本类型,每个可以带4个通道。一共可以使用32个,类型。
      • 带通道的类型定义如下:
        • #define CV_8UC1 CV_MAKETYPE(CV_8U,1)
  3. OpenCV自带的数据定义:

    • typedef uint32_t uint
    • typedef signed char schar
    • typedef unsigned char uchar
    • typedef unsigned short ushort
    • typedef int64_t int64
    • typedef uint64_t uint64
  4. 使用例子

    #include <QApplication>
    #include <QDialog>
    #include <QLabel>
    // #include <QImage>    // 不需要引入
    // #include <QPixmap>
    ////////////////////////////////
    #include <opencv2/opencv.hpp>
    ///////////////////////////////
    #include "./m01_constructor/m01_constructor.h"

    #include <iostream>

    int main(int argc, char* argv[]) {
        // 初始化QT应用
        QApplication app(argc, argv);
        // 创建一个对话框
        QDialog  dlg;
        // 设置对话框大小
        dlg.setWindowTitle("OpenCV之Mat图像表示");
        dlg.setGeometry(100,100,400,300);

        // 显示图片的标签框
        QLabel lbl_image("显示图片", &dlg); // 以dlg作为父窗体
        // 图片框大小位置
        lbl_image.setGeometry(0,0, 400,300);


        /* 图像显示 */
        cv::Mat src_image;
        cv::Mat image;
        ////////////////////////////////////
        src_image = create_matrix();    // 调用矩阵构造函数
        ////////////////////////////////////
        // 转换图像颜色空间
        cv::cvtColor(src_image, image, cv::COLOR_BGRA2RGBA);  // opencv读取数据的格式是BGRA,需要转换为QT能处理的格式
        // 把图像转换: cv::Mat -> QImage -> QPixmap -> 设置到QLabel
        const uchar *u_image = image.data;
        QImage img_buffer(u_image, image.cols, image.rows, QImage::Format_RGBA8888);
        QPixmap img_map = QPixmap::fromImage(img_buffer);
        lbl_image.setPixmap(img_map);


        lbl_image.setScaledContents(true);
        // std::cout<< image.dims<<std::endl;
        // std::cout<< image.depth()<<std::endl;
        dlg.show();
        return app.exec();
    }

    #ifndef M01_CONSTRUCTOR_H
    #define M01_CONSTRUCTOR_H
    #include <opencv2/opencv.hpp>

    cv::Mat create_matrix();
    #endif
    #include "m01_constructor.h"
    cv::Mat create_matrix(){
        int rows = 300;             // 图像高度
        int cols = 400;             // 图像宽度
        int type = CV_8UC4;         // 像素类型(宏没有命名空间)
        uchar *data = new uchar[rows * cols * 4];  // 像素4通道unsigned char(0-255之间);
        /* data暂时不初始化,使用分配时的脏数据 */
        /* data初始化 */
        bzero(data, rows * cols * 4);  // 初始化为0(透明的黑色)
        // memset(data, 255, rows * cols * 4);   // 全部初始化为255,不透明的白色
        // 循环初始化(4通道中第1通道与第4通道为255,其他为0)
        for(int idx=0; idx <rows * cols * 4/2; idx++){   // 初始化一半
            if(idx % 4 == 0 || idx % 4 == 3){
                data[idx] = 255;
            }
        }
        cv::Mat  img(rows, cols, type, data, cv::Mat::AUTO_STEP);   // AUTO_STEP定义在Mat类内部的
        return img;
        /*
            data不用释放,由cv::Mat管理;
         */
    }

  1. 数据产生的图像
    • 注意:Mat表示图像,颜色顺序式B G R A
使用Mat创建的图像

重载构造器

#include "m01_constructor.h"
cv::Mat create_matrix(){
    int rows = 300;             // 图像高度
    int cols = 400;             // 图像宽度
    int type = CV_8UC4;         // 像素类型(宏没有命名空间)
    cv::Scalar pixel(0,255,0, 255);
    cv::Mat  img(rows, cols, type, pixel); 
    return img;
    /*
        data不用释放,由cv::Mat管理;
     */
}

数据属性

行列

  1. 属性:
    1. int cols
    2. int rows
  2. 相关函数

数据

  1. 属性:
    • uchar * data
  2. 相关函数:
    • uchar * ptr (int i0=0)
      • 参数使用默认的0,表示第一行的数据位置。

维度

标记位

其他属性

  1. MatSize size
  2. MatStep step
  3. UMatData * u

使用函数可以访问的属性

  1. int channels () const
    • 通道数
  2. int depth () const
    • 深度原色基本类型,如下几种,不包含通道信息:
      • 0:CV_8U - 8-bit unsigned integers ( 0..255 )
      • 1:CV_8S - 8-bit signed integers ( -128..127 )
      • 2:CV_16U - 16-bit unsigned integers ( 0..65535 )
      • 3:CV_16S - 16-bit signed integers ( -32768..32767 )
      • 4:CV_32S - 32-bit signed integers ( -2147483648..2147483647 )
      • 5:CV_32F - 32-bit floating-point numbers ( -FLT_MAX..FLT_MAX, INF, NAN )
      • 6:CV_64F - 64-bit floating-point numbers ( -DBL_MAX..DBL_MAX, INF, NAN )
  3. size_t elemSize () const
    • 每个元素的字节数。
  4. size_t elemSize1 () const
    • 每个通道的字节数;
  5. bool empty () const
    • 判定数据是否为空;
  6. size_t total () const
    • 返回数组的元素个数;
  7. int type () const
    • 返回矩阵元素的类型;该类型不是C++的数据类型,是上面讲述的带同到信息的类型。

    // 测试Mat的属性:
    std::cout<<"行列:(" << src_image.rows << ","<< src_image.cols <<")"<<std::endl;
    std::cout<<"维度,通道与深度:(" << src_image.dims << "," << src_image.channels() << "," << src_image.depth() << ")" <<std::endl;
    std::cout<<"类型:"<< src_image.type() <<", 是否是CV_8UC4?" <<(CV_8UC4 == src_image.type()) << std::endl;
    std::cout<<"元素字节数,通道字节数,总的元素个数:" << src_image.elemSize() <<","<< src_image.elemSize1() << "," << src_image.total() <<std::endl;
    // std::cout<<":"<<0<<std::endl;

localhost:05Mat yangqiang$ ./main
行列:(300,400)
维度,通道与深度:(2,4,0)
类型:24, 是否是CV_8UC4?1
元素字节数,通道字节数,总的元素个数:4,1,120000
localhost:05Mat yangqiang$ 

flags属性的使用

  1. flags属性包含的数据:

    • the magic signature
    • continuity flag
    • depth
    • number of channels
      • 就是type标记位,需要处理下才能得到通道数(位运算)。
  2. 位遮罩

    • MAGIC_MASK = 0xFFFF0000,
    • TYPE_MASK = 0x00000FFF,
      • int type () const
    • DEPTH_MASK = 7
      • int depth () const
    • CONTINUOUS_FLAG = CV_MAT_CONT_FLAG
      • bool isContinuous () const
  3. 使用例子

    int flags = src_image.flags;
    std::cout << "标记值:" << flags << std::endl;
    std::cout << "魔法值:" << (flags & cv::Mat::MAGIC_MASK) << "," << cv::Mat::MAGIC_VAL << std::endl;  
    std::cout << "深度值:" << (flags & cv::Mat::DEPTH_MASK) << "," << CV_8U << std::endl;  
    std::cout << "类型值:" << (flags & cv::Mat::TYPE_MASK) << "," << CV_8UC4 << std::endl;  
    // 矩阵是否连续,flags & cv::Mat::CONTINUOUS_FLA 不等于0都是连续的。
    std::cout << "连续值:" << (flags & cv::Mat::CONTINUOUS_FLAG) << "," << cv::Mat::CONTINU

数据访问

at函数

  1. at函数的基本声明
    template<typename _Tp > _Tp & at (int row, int col)
  1. at函数的重载声明

    • template<typename _Tp > _Tp & at (int i0=0)
    • template<typename _Tp > const _Tp & at (int i0=0) const
    • template<typename _Tp > _Tp & at (int row, int col)
    • template<typename _Tp > const _Tp & at (int row, int col) const
    • template<typename _Tp > _Tp & at (int i0, int i1, int i2)
    • template<typename _Tp > const _Tp & at (int i0, int i1, int i2) const
    • template<typename _Tp > _Tp & at (const int *idx)
    • template<typename _Tp > const _Tp & at (const int *idx) const
    • template<typename _Tp , int n>_Tp & at (const Vec< int, n > &idx)
    • template<typename _Tp , int n>const _Tp & at (const Vec< int, n > &idx) const
    • template<typename _Tp >_Tp & at (Point pt)
    • template<typename _Tp >const _Tp & at (Point pt) const
  2. 关于元访问的封装

    • 在opencv中提供了Vec模板类来封装矩阵元素的访问,尤其是3这种无法对齐的数据,访问起来特别方便。
      • Vec从cv::Matx类。
      • 模板:Vec< T, cn > () const
        • T :数据类型
        • cn : 元素个数。
      • 提供[ ]运算符,用来访问Vec中的元素。
  3. at函数的使用例子

    • 把蓝色通道设置为0;
    void access_at(cv::Mat &mat){
        int rows = mat.rows;
        int cols = mat.cols;
        int channels = mat.channels();  // 三通道
        int depth = mat.depth();
        std::cout<< depth << "," << channels << std::endl;
        for(int y = 0; y < rows; y++){
            for(int x = 0; x < cols; x++){
                cv::Vec<u_char, 3>& pixel = mat.at<cv::Vec<u_char, 3> >(y, x);
                // std::cout<<(unsigned int)pixel[0]<< ",";
                // std::cout<<(unsigned int)pixel[1]<< ",";
                // std::cout<<(unsigned int)pixel[2]<< ",";
                // std::cout<<std::endl;
                pixel[0] = 0;   // B把蓝色通道设置为0
                // pixel[1] = 0;   // G绿色通道
                // pixel[2] = 0;   // R红色通道

            }
        }
    }

使用at修改图像所有像素的蓝色通道为0的效果

ptr函数

    void access_ptr(cv::Mat &mat){
        int rows = mat.rows;
        int cols = mat.cols;
        for(int y = 0; y < rows; y++){
            for(int x = 0; x < cols; x++){
                cv::Vec<u_char, 3>* pixel = mat.ptr<cv::Vec<u_char, 3> >(y, x);
                (*pixel)[0] = 0;   // B把蓝色通道设置为0
            }
        }
    }

row/col与rowRange/colRange函数

  1. row函数使用例子
    void access_row(cv::Mat &mat){
        int rows = mat.rows;
        int cols = mat.cols;
        for(int y = 0; y < rows; y++){
            cv::Mat row = mat.row(y);
            for(int x = 0; x < cols; x++){
                cv::Vec<u_char, 3>& pixel = row.at<cv::Vec<u_char, 3> >(x);
                pixel[0] = 0;   // B把蓝色通道设置为0
            }
        }
    }
  1. rowRange使用例子
    void access_row(cv::Mat &mat){
        int rows = mat.rows;
        int cols = mat.cols;
        // 取部分矩阵
        cv::Mat part_mat = mat.rowRange(100,200); 

        for(int y = 0; y < 100; y++){   // 注意不要越界
            cv::Mat row = part_mat.row(y);
            for(int x = 0; x < cols; x++){
                cv::Vec<u_char, 3>& pixel = row.at<cv::Vec<u_char, 3> >(x);
                pixel[0] = 0;   // B把蓝色通道设置为0
                pixel[1] = 0;   // B把蓝色通道设置为0
            }
        }
    }
使用row函数修改图像局部区域的蓝色与绿色通道为0的效果

begin与end函数

  1. begin函数定义:
    • 模板函数。
    • end函数类似。
template<typename _Tp > MatConstIterator_< _Tp >    begin () const
  1. begin与end函数返回的是迭代器。迭代器的最大好处就是提供了位置移动的运算符:

    • ++:分前后
    • --:分前后
    • +=
    • -=
    • []:也无法修改:
      • const _Tp & operator[] (ptrdiff_t i) const
    • *:取值运算符;这个取值返回的数据不能修改:
      • const _Tp & operator* () const
    • 返回位置:
      • Point pos () const
  2. 使用cv::Mat_的内部迭代器

    • 可以修改访问数据
  3. 使用例子

    void access_begin_end(cv::Mat &mat){
        // 获取元素开始的迭代器
        // cv::MatConstIterator_<cv::Vec<u_char, 3> > pixel = mat.begin<cv::Vec<u_char, 3> >(); // 使用外部迭代器,使用[]无法修改。
        cv::Mat_<cv::Vec<u_char, 3> >::iterator pixel = mat.begin<cv::Vec<u_char, 3> >();  // 使用内部迭代器,可以访问成员
        // 循环迭代
        do{
            (*pixel)[0] = 0;   // B把蓝色通道设置为0
            (*pixel)[1] = 0;   // B把蓝色通道设置为0
            pixel++;
        } while(pixel != mat.end<cv::Vec<u_char, 3> >());

    }

forEach函数

  1. 函数原型定义
    • 其中的函数对象就是回调函数(C++11增加了箭头函数)。

    template<typename _Tp , typename Functor > void     forEach (const Functor &operation)
    template<typename _Tp , typename Functor > void     forEach (const Functor &operation) const

  1. 关于函数对象Functor

    • 包含特定的运算符:
      • void operator ()(.....) const {
  2. functor使用例子:

    • 位置根据Mat的维数来决定。
    • 使用struct与class注意下区分就行。
    // Functor(这个是并行调用的)
    struct PixelFunctor{
        void operator ()(cv::Vec<u_char, 3>& pixel, const int *pos) const{ // 传递像素与像素在矩阵中位置
            // std::cout << pos[0] << "," << pos[1] << std::endl;  (位置)// 因为并行,打印可能是错乱的。
            pixel[0] = 0;
        }
    };   // 记得这个分号

    void access_foreach(cv::Mat &mat){
        struct PixelFunctor functor;
        mat.forEach<cv::Vec<u_char, 3> >(functor);
    }

  1. C++11的lambda表达式使用例子(怪异的箭头函数)
    • 其中[ ] 表示闭包访问的本地局部变量。简易使用&按引用传递,=按值传递不建议。

void access_foreach(cv::Mat &mat){
    int a = 20;
    mat.forEach<cv::Vec<u_char, 3> >([&a](cv::Vec<u_char, 3>& pixel, const int *pos)->void{
        pixel[1] = 0;
        a = 30;  // [&]知名本地局部变量按照引用访问
    });
    std::cout<<a<<std::endl;  // 30
}

  1. 函数指针的例子

    void function_ptr(cv::Vec<u_char, 3>& pixel, const int *pos){
        pixel[0] = 0;
    }
    void access_foreach(cv::Mat &mat){
        mat.forEach<cv::Vec<u_char, 3> >(&function_ptr);
    }

运算符

类型转换运算符

常规类型转换运算符


    template<typename _Tp, int m, int n> operator Matx< _Tp, m, n > () const
    template<typename _Tp, std::size_t _Nm> operator std::array< _Tp, _Nm > () const
    template<typename _Tp> operator std::vector< _Tp > () const
    template<typename _Tp, int n>operator Vec< _Tp, n > () const

带参类型转换运算符

    Mat operator() (Range rowRange, Range colRange) const
    Mat     operator() (const Rect &roi) const
    Mat     operator() (const Range *ranges) const
    Mat     operator() (const std::vector< Range > &ranges) const

赋值运算符

    Mat &   operator= (const Mat &m)
    Mat &   operator= (const MatExpr &expr)
    Mat &   operator= (const Scalar &s)
    Mat &   operator= (Mat &&m)

运算符使用例子

    /////////////////////////////////////////////////////
    // ()类型转换运算符
    cv::Mat sub_image = src_image(cv::Range(100, 200), cv::Range(100, 200));  // 返回的是引用
    // 赋值运算符
    sub_image = cv::Scalar(255,0,0);
    /////////////////////////////////////////////////////
使用重载运算符()访问局部区域,并修改为单一颜色的效果

矩阵操作

数据操作

拷贝与克隆

    void    copyTo (OutputArray m) const 
    void    copyTo (OutputArray m, InputArray mask) const

    Mat     clone () const CV_NODISCARD

修改值

    Mat &   setTo (InputArray value, InputArray mask=noArray())

添加与删除值

    void    pop_back (size_t nelems=1)
    void    push_back (const _Tp &elem)

    template<typename _Tp > void    push_back (const Mat_< _Tp > &elem)
    template<typename _Tp > void    push_back (const std::vector< _Tp > &elem)
    void    push_back (const Mat &m)
    void    push_back_ (const void *elem)

变形


Mat     reshape (int cn, int rows=0) const
Mat     reshape (int cn, int newndims, const int *newsz) const 
Mat     reshape (int cn, const std::vector< int > &newshape) const

改变大小

void    resize (size_t sz) 
void    resize (size_t sz, const Scalar &s)
    // const Scalar &s是变大后,新空间的填充值

矩阵运算操作

矩阵乘法

    MatExpr     mul (InputArray m, double scale=1) const

向量点积

    double  dot (InputArray m) const

叉积

    Mat     cross (InputArray m) const

逆矩阵

    MatExpr     inv (int method=DECOMP_LU) const

矩阵转置

    MatExpr     t () const

关于MatExpr

正经的矩阵类MatExpr

初始化与释放操作

分配

    void    create (Size size, int type)
    void    create (int ndims, const int *sizes, int type)
    void    create (const std::vector< int > &sizes, int type)

释放

    void    deallocate ()  // 内部调用,一般使用release
    void    release ()

工具函数

创建单位矩阵

    static MatExpr  eye (int rows, int cols, int type)
    static MatExpr  eye (Size size, int type)

创建对角矩阵

    static Mat  diag (const Mat &d) 

创建1矩阵

    static MatExpr  ones (int rows, int cols, int type)
    static MatExpr  ones (Size size, int type)
    static MatExpr  ones (int ndims, const int *sz, int type)

创建零矩阵

    static MatExpr  zeros (int rows, int cols, int type) 
    static MatExpr  zeros (Size size, int type)
    static MatExpr  zeros (int ndims, const int *sz, int type)

附录:

    #include <opencv2/opencv.hpp>

    int main(int argc, char* argv[]) {
        // 初始化列表
        std::initializer_list<int> v1 = {1,2,3,4};
        cv::Mat m1(v1);
        cv::Mat m2({1,2,3,4,5});

        cv::Mat_<int> m3={1,2,3,4};  // 这种方式对Mat错误(语法没有问题),但是要求显式选择构造器(模板类型歧义造成)

        // MatCommaInitializer_ 必须由矩阵产生
        cv::Mat M = (cv::Mat_<int>(3,3) << 1, 0, 0, 0, 1, 0, 0, 0, 1);
        return 0;
    }


上一篇下一篇

猜你喜欢

热点阅读