opencv学习之core模块核心功能1

目录

core模块核心功能

1Mat - 基本图像容器

1.1.1 Mat

某些时候你仍会想拷贝矩阵本身(不只是信息头和矩阵指针),这时可以使用函数 clone() 或者 copyTo() 。

Mat F = A.clone();
Mat G;
A.copyTo(G);
  • OpenCV函数中输出图像的内存分配是自动完成的(如果不特别指定的话)。
  • 使用OpenCV的C++接口时不需要考虑内存释放问题。
  • 赋值运算符和拷贝构造函数( ctor )只拷贝信息头。
  • 使用函数 clone()或者 copyTo()来拷贝一副图像的矩阵。
1.1.2存储方法

RGB颜色空间是最常用的一种颜色空间,它的基色是红色、绿色和蓝色,有时为了表示透明颜色也会加入第四个元素 alpha (A)。

  • RGB是最常见的,这是因为人眼采用相似的工作机制,它也被显示设备所采用。
  • HSV和HLS把颜色分解成色调、饱和度和亮度/明度。这是描述颜色更自然的方式,比如可以通过抛弃最后一个元素,使算法对输入图像的光照条件不敏感。
  • YCrCb在JPEG图像格式中广泛使用。
  • CIE Lab*是一种在感知上均匀的颜色空间,它适合用来度量两个颜色之间的 距离

最小的数据类型是 char ,占一个字节或者8位,可以是无符号型(0到255之间)或有符号型(-127到+127之间)。尽管使用三个 char 型元素已经可以表示1600万种可能的颜色(使用RG颜色空间),但若使用float(4字节,32位)或double(8字节,64位)则能给出更加精细的颜色分辨能力。

1.1.3创建一个Mat对象

可以通过 Mat 的运算符 << 来实现看实际值。这只对二维矩阵有效。

Mat 不但是一个很赞的图像容器类,它同时也是一个通用的矩阵类,所以可以用来创建和操作多维矩阵。

Mat构造函数

 Mat M(2,2, CV_8UC3, Scalar(0,0,255)); 
 cout << "M = " << endl << " " << M << endl << endl;   

对于二维多通道图像,首先要定义其尺寸,即行数和列数。然后,需要指定存储元素的数据类型以及每个矩阵点的通道数。 CV_8UC3 表示使用8位的 unsigned char 型,每个像素由三个元素组成三通道。预先定义的通道数可以多达四个。 ==Scalar 是个short型vector。==指定这个能够使用指定的定制化值来初始化矩阵。当然,如果你需要更多通道数,你可以使用大写的宏并把通道数放在小括号中。

int sz[3] = {2,2,2}; 
Mat L(3,sz, CV_8UC(1), Scalar::all(0));//第一个是多维的维度

Create() function: 函数

 M.create(4,4, CV_8UC(2));
 cout << "M = "<< endl << " "  << M << endl << endl;

这个创建方法不能为矩阵设初值,它只是在改变尺寸时重新为矩阵数据开辟内存。

MATLAB形式

	Mat E = Mat::eye(4, 4, CV_64F);    //单位阵
    cout << "E = " << endl << " " << E << endl << endl;
    Mat O = Mat::ones(2, 2, CV_32F);    //全部为1
    cout << "O = " << endl << " " << O << endl << endl;
    Mat Z = Mat::zeros(3,3, CV_8UC1);//全部为0
    cout << "Z = " << endl << " " << Z << endl << endl;

逗号分隔的初始化函数

Mat C = (Mat_<double>(3,3) << 0, -1, 0, -1, 5, -1, 0, -1, 0); 
    cout << "C = " << endl << " " << C << endl << endl;

使用 clone()或者 copyTo()

Mat RowClone = C.row(1).clone();
cout << "RowClone = " << endl << " " << RowClone << endl << endl;

1.1.4 格式化打印

调用函数 randu()来对一个矩阵使用随机数填充,需要指定随机数的上界和下界:

 Mat R = Mat(3, 2, CV_8UC3);
 randu(R, Scalar::all(0), Scalar::all(255));
cout << "R(default)="<<endl<<R<< endl << endl;//默认方式
cout << "R(python)="<<endl<< format(R,"python") << endl << endl;//Python
cout << "R(csv)="<<endl<<format(R,"csv")<<endl<<endl;//逗号分隔
cout << "R(numpy)="<< endl << format(R,"numpy")<<endl<<endl;//Numpy格式
cout << "R(c)="<< endl << format(R,"C") << endl << endl;//C语言
1.1.5打印其它常用项目
Point2f P(5, 1);//2维点
cout << "Point (2D) = " << P << endl << endl;
Point3f P3f(2, 6, 7);//3维点
cout << "Point (3D) = " << P3f << endl << endl;
vector<float> v;//基于cv::Mat的std::vector
v.push_back( (float)CV_PI);   v.push_back(2);    v.push_back(3.01f); 
cout << "Vector of floats via Mat = " << Mat(v) << endl << endl;
vector<Point2f> vPoints(20);//std::vector点
for (size_t E = 0; E < vPoints.size(); ++E)
vPoints[E] = Point2f((float)(E * 5), (float)(E % 7));
cout << "A vector of 2D Points = " << vPoints << endl << endl;

2 OpenCV如何扫描图像、利用查找表和计时

2.1 测试用例

有时候,仅用这些颜色的一小部分,就足以达到同样效果。常用的一种方法是 颜色空间缩减 。其做法是:将现有颜色空间值除以某个输入值,以获得较少的颜色数。例如,颜色值0到9可取为新值0,10到19可取为10,以此类推。

uchar (无符号字符,即0到255之间取值的数)类型的值除以 int 值,结果仍是 char 。因为结果是char类型的,所以求出来小数也要向下取整。

简单的颜色空间缩减算法就可由下面两步组成:一、遍历图像矩阵的每一个像素;二、对像素应用上述公式。

对于较大的图像,有效的方法是预先计算所有可能的值,然后需要这些值的时候,利用查找表直接赋值即可。查找表是一维或多维数组,存储了不同输入值所对应的输出值,其优势在于只需读取、无需计算。

OpenCV提供了两个简便的可用于计时的函数 getTickCount()和 getTickFrequency()。第一个函数返回你的CPU自某个事件(如启动电脑)以来走过的时钟周期数,第二个函数返回你的CPU一秒钟所走的时钟周期数。

2.2图像矩阵是如何存储在内存之中的

图像矩阵的大小取决于我们所用的颜色模型,确切地说,取决于所用通道数。

子列的通道顺序是反过来的:BGR而不是RGB。因为内存足够大,可实现连续存储,因此,图像的各行就能一行一行地连接起来,形成一个长行。连续存储有助于提升图像扫描速度,可以使用 isContinuous()来去判断矩阵是否是连续存储的.

2.3高效的方法 Efficient Way

推荐的效率最高的查找表赋值方法是经典的C风格运算符[](指针)。

Mat& ScanImageAndReduceC(Mat& I, const uchar* const table)
{
    // accept only char type matrices
    CV_Assert(I.depth() != sizeof(uchar));     

    int channels = I.channels();
    int nRows = I.rows * channels; 
    int nCols = I.cols;
    if (I.isContinuous())
    {
        nCols *= nRows;
        nRows = 1;         
    }
    int i,j;
    uchar* p; 
    for( i = 0; i < nRows; ++i)
    {
        p = I.ptr<uchar>(i);
        for ( j = 0; j < nCols; ++j)
        {
            p[j] = table[p[j]];             
        }
    }
    return I; 
}

我们获取了每一行开始处的指针,然后遍历至该行末尾。如果矩阵是以连续方式存储的,我们只需请求一次指针、然后一路遍历下去就行。彩色图像的情况有必要加以注意:因为三个通道的原因,我们需要遍历的元素数目也是3倍。

另外一种方法来实现遍历功能,就是使用 data , data会从 Mat 中返回指向矩阵第一行第一列的指针。注意如果该指针为NULL则表明对象里面无输入,所以这是一种简单的检查图像是否被成功读入的方法。但是这种方法编写的代码可读性方面差,性能表现上并不明显优于前一种。

2.4 迭代法 The iterator (safe) method

迭代法则被认为是一种以更安全的方式来实现这一功能。在迭代法中,你所需要做的仅仅是获得图像矩阵的begin和end,然后增加迭代直至从begin到end。将*操作符添加在迭代指针前,即可访问当前指向的内容。

Mat& ScanImageAndReduceIterator(Mat& I, const uchar* const table)
{
    // accept only char type matrices
    CV_Assert(I.depth() != sizeof(uchar));     
    
    const int channels = I.channels();
    switch(channels)
    {
    case 1: 
        {
            MatIterator_<uchar> it, end; 
            for( it = I.begin<uchar>(), end = I.end<uchar>(); it != end; ++it)
                *it = table[*it];
            break;
        }
    case 3: 
        {
            MatIterator_<Vec3b> it, end; 
            for( it = I.begin<Vec3b>(), end = I.end<Vec3b>(); it != end; ++it)
            {
                (*it)[0] = table[(*it)[0]];
                (*it)[1] = table[(*it)[1]];
                (*it)[2] = table[(*it)[2]];
            }
        }
    }
    
    return I; 
}

对于彩色图像中的一行,每列中有3个uchar元素,这可以被认为是一个小的包含uchar元素的vector,在OpenCV中用 Vec3b 来命名。如果要访问第n个子列,我们只需要简单的利用[]来操作就可以。需要指出的是,OpenCV的迭代在扫描过一行中所有列后会自动跳至下一行,所以说如果在彩色图像中如果只使用一个简单的 uchar 而不是 Vec3b 迭代的话就只能获得蓝色通道(B)里的值。

2.5 通过相关返回值的On-the-fly地址计算

它的基本用途是要确定你试图访问的元素的所在行数与列数。它本来是被用于获取或更改图像中的随机元素。不推荐被用来进行图像扫描。

Mat& ScanImageAndReduceRandomAccess(Mat& I, const uchar* const table)
{
    // accept only char type matrices
    CV_Assert(I.depth() != sizeof(uchar));     

    const int channels = I.channels();
    switch(channels)
    {
    case 1: 
        {
            for( int i = 0; i < I.rows; ++i)
                for( int j = 0; j < I.cols; ++j )
                    I.at<uchar>(i,j) = table[I.at<uchar>(i,j)];
            break;
        }
    case 3: 
        {
         Mat_<Vec3b> _I = I;
            
         for( int i = 0; i < I.rows; ++i)
            for( int j = 0; j < I.cols; ++j )
               {
                   _I(i,j)[0] = table[_I(i,j)[0]];
                   _I(i,j)[1] = table[_I(i,j)[1]];
                   _I(i,j)[2] = table[_I(i,j)[2]];
            }
         I = _I;
         break;
        }
    }
    
    return I;
}

该函数输入为数据类型及需求元素的坐标,返回的是一个对应的值-如果用 get 则是constant,如果是用 set 、则为non-constant.

为避免反复输入数据类型和at带来的麻烦和浪费的时间,OpenCV 提供了:basicstructures:Mat_ data type. 它同样可以被用于获知矩阵的数据类型,你可以简单利用()操作返回值来快速获取查询结果.可以利用 at()函数来用同样速度完成相同操作. 它仅仅是为了让懒惰的程序员少写点 >_< .

2.6 核心函数LUT(The Core Function)

是最被推荐的用于实现批量图像元素查找和更该操作图像方法。OpenCV 提供里一个函数直接实现给定值的替换,并不需要你自己扫描图像,就是:operationsOnArrays:LUT() ,一个包含于core module的函数.

 Mat lookUpTable(1, 256, CV_8U);//首先我们建立一个mat型用于查表
    uchar* p = lookUpTable.data; 
    for( int i = 0; i < 256; ++i)
        p[i] = table[i];
 LUT(I, lookUpTable, J);//然后我们调用函数 (I 是输入 J 是输出)

尽量使用 OpenCV 内置函数. 调用LUT 函数可以获得最快的速度. 喜欢使用指针的方法来扫描图像,迭代法是一个不错的选择,不过速度上较慢。

3 矩阵的掩码操作

矩阵的掩码操作很简单。其思想是:==根据掩码矩阵(也称作核)重新计算图像中每个像素的值。==掩码矩阵中的值表示近邻像素值(包括该像素自身的值)对新像素值有多大影响。从数学观点看,我们用自己设置的权值,对像素邻域内的值做了个加权平均。

在这里插入图片描述

虽然这两种形式是完全等价的,但在大矩阵情况下,下面的形式看起来会清楚得多。一种方法是用基本的像素访问方法,另一种方法是用 filter2D 函数。

3.1基本方法
void Sharpen(const Mat& myImage,Mat& Result)
{
    CV_Assert(myImage.depth() == CV_8U);  // 仅接受uchar图像,若为false则跑出错误

    Result.create(myImage.size(),myImage.type());//创建与输入相同大小类型的输出图像
    const int nChannels = myImage.channels();

    for(int j = 1 ; j < myImage.rows-1; ++j)
    {
        const uchar* previous = myImage.ptr<uchar>(j - 1);
        const uchar* current  = myImage.ptr<uchar>(j    );
        const uchar* next     = myImage.ptr<uchar>(j + 1);

        uchar* output = Result.ptr<uchar>(j);

        for(int i= nChannels;i < nChannels*(myImage.cols-1); ++i)
        {
            *output++ = saturate_cast<uchar>(5*current[i]
                         -current[i-nChannels] - current[i+nChannels] - previous[i] - next[i]);
        }
    }

    Result.row(0).setTo(Scalar(0));
    Result.row(Result.rows-1).setTo(Scalar(0));
    Result.col(0).setTo(Scalar(0));
    Result.col(Result.cols-1).setTo(Scalar(0));
}

在图像的边界上,上面给出的公式会访问不存在的像素位置(比如(0,-1))。因此我们的公式对边界点来说是未定义的。一种简单的解决方法,是不对这些边界点使用掩码,而直接把它们设为0。

Result.row(0).setTo(Scalar(0));             // 上边界
Result.row(Result.rows-1).setTo(Scalar(0)); // 下边界
Result.col(0).setTo(Scalar(0));             // 左边界
Result.col(Result.cols-1).setTo(Scalar(0)); // 右边界
3.2filter2D函数

OpenCV也有个用到了滤波器掩码(某些场合也称作核)的函数。

Mat kern = (Mat_<char>(3,3) <<  0, -1,  0,//定义一个表示掩码的 Mat 对象:
                               -1,  5, -1,
                                0, -1,  0);
filter2D(I, K, I.depth(), kern );//调用 filter2D 函数,参数包括输入、输出图像以及用到的核

它还带有第五个可选参数——指定核的中心,和第六个可选参数——指定函数在未定义区域(边界)的行为。代码更加清晰简洁、通常比 自己实现的方法速度更快。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值