Bootstrap

OpenCV入门: Mat数据类型及其转换,访问

1. 总结

先贴上我总结的Opencv的数据类型, 主要是针对不同Mat类型进行新建,修改和访问时使用, 更详细的数据访问见下文:
OpenCV数据类型及其 对应数字

2. CV_8UC3解说

新建一个CV_8UC3型的cv::Mat, 其中U代表了unsigned char型的数据, 其表示的范围为0 到 255; C3表示三个通道, 也就是同一个位置能存放几个数字;
最常见的使用位置是用于保存图像类型, C3用于存储RGB彩色图像, C1用于存储灰度图像, 为什么用uchar型数据来存储图像数据, 这里有详细的解释.
总结来讲,就是int型的数据表示范围过大, 如果使用int型, 一副图像所占内存有点大, 而 uchar型数据范围刚好用来保存256级的像素, 节省空间;
具体的访问:

// CV_8UC3 的数据类型集访问
std::string image_path = myRGBimagePath;
cv::Mat input_image = cv::imread(image_path, -1);
std::cout << "input_image type: "<< input_image.type() << std::endl; //CV_8UC3 16
// 对uchar数据的访问, 三通道, 数据为uchar型, 显示时要用(int)进行强制转换
cv::Vec3b a_pixel = input_image.at<cv::Vec3b>(0,0); //127, 130, 128
//三个数字位于相同的像素位置, 但是属于不同的通道
std::cout << "pixel in center: " << (int)a_pixel[0] << ", "<<  (int)a_pixel[1] << ", " <<  (int)a_pixel[2] << std::endl;
//3. 8UC3转换成8UC1, 访问时直接使用uchar
cv::Mat input_gray(input_image.size(), CV_8UC1);
cv::cvtColor(input_image,input_gray,CV_RGB2GRAY);
std::cout << "input_gray <0,0> " << (int)input_gray.at<uchar>(0,0) <<std::endl;

3. CV_8SC2详解

接下来说一下不常见的CV_8S类型的数据, 以CV_8SC2为例, S代表了有符号 signed char, 8就代表了这个数据可以用8bit表示, 上面的unsigned char 型, 8bits 表示范围是0~255, 那么这里的signed char型表示的范围自然就正负各一半: - 128 到 127; C2代表了两个通道; 下面举个例子:
CV_8SC2
其中的每个元素都是8位的有符号整型;
假如这是5*5的Mat, 那么(0,0) 位置上有两个元素存放, 为(-85, -127);

这里对signed char型多通道Mat的访问, 如何用at实现,我还不太清楚, 这也就是为什么上面表格中第二行没有对应的Vec, 有了解的欢迎不吝赐教啊!!

这里举一个用行指针实现的Mat的访问:

//不同的数据类型的Mat可以通过convertTo() 函数进行转换
cv::Mat input_image_schar;
input_image.convertTo(input_image_schar, CV_8SC3);
//先读取第0行的数据
schar* mypixel = input_image_schar.ptr<schar>(0);
std::cout << "mypixel = " << (int)mypixel[0] << "," <<(int)mypixel[1] << ","<<(int)mypixel[2] << "," << std::endl;

有更多基础知识想要了解的,可以看这个OpenCV Tutorial (查看图片可能需要翻墙)

4. InputArray 与cv::Mat

cv::InputArray() 是opencv建立的一个更包容的类, 一般用在OpenCV的函数接口处; 可以承接Mat、Mat_、Mat_<T, m, n>、vector <T>、vector<vector<T>、vector<Mat> 类型的数据; 这里简单地与Mat做一个转换:

std::vector<cv::Point2f> myPoints_xy;
myPoints_xy.push_back(cv::Point2f(2.45,3.7));
myPoints_xy.push_back(cv::Point2f(3.68,8.8));
myPoints_xy.push_back(cv::Point2f(8.55,8.12));
//可以利用 vector<cv::Point2f>来初始化一个InputArray
cv::InputArray myArray_xy(myPoints_xy); 
std::cout << "myArray_xy type: " << myArray_xy.type() << "myArray_xy size: " << myArray_xy.size()<< std::endl;
cv::Mat myMat_xy;//获取到矩阵数据并转换成CV_32FC2的类型
myArray_xy.getMat().convertTo(myMat_xy,CV_32FC2);
//CV_32F数据类型的访问也是比较简单易懂, 直接找到对应的Vec即可
cv::Vec2f tmpPoint = myMat_xy.at<cv::Vec2f>(0,0);
std::cout << "tmpPoint is " << tmpPoint[0] << "," << tmpPoint[1] << std::endl;//

output: 
myArray_xy.type() = 13: CV_32FC2 size: [3 x 1]
tmpPoint is 2.45,3.7

5. Mat的不同访问方式

int main()
{
    cv::Mat matF(3, 3, CV_32F);
    randu(matF, Scalar(0), Scalar(10));

    int rowIdx = 1;
    int colIdx = 1;

    // 1. 直接使用at<数据类型>(行值,列值)来访问
    float f1 = matF.at<float>(rowIdx, colIdx);

    // 2. 使用.data 获取数据存储区域的第一个数据的位置, step1() 代表的是一行总共有几个数据,3x3的CV_32FC4 一行有4*3=12个数据
    float* fData2 = (float*)matF.data;
    float f2 = fData2[rowIdx*matF.step1() + colIdx];

    // 3. 使用.ptr<数据类型>(0) 获取第一个元素的地址
    float* fData3 = matF.ptr<float>(0);
    float f3 = fData3[rowIdx*matF.step1() + colIdx];

    // 4.使用.ptr<数据类型>(行数) 的行指针形式进行访问
    float* fData4 = matF.ptr<float>(rowIdx);
    float f4 = fData4[colIdx];

    // 5. typedef Mat_<float> Mat1f, 数据访问会更加快速
    Mat1f mm(matF); // Or directly create like: Mat1f mm(3, 3);
    float f5 = mm(rowIdx, colIdx);

    // f1 == f2 == f3 == f4 == f5

    return 0;
}

这部分代码参考: SO上的问题
对于Mat的访问想进一步了解的, 可以参考我的这篇小白学习OpenCV Mat, 可能对于Step, Channel那些会有进一步的了解.

有什么问题,欢迎大家多多交流~~

;