opencv3-core之基本操作

这一篇打算将core部分的例子说完,这都是基于《opencv2.4.9tutorial.pdf》中的core部分,其实这些例子后期都很稳定的,也就是说就算是2.3.1和2.4.10 ,这几个例子不会变,变化的是新增函数啊什么的,所以无需担心这里的例子是否不适用新版本(opencv3按照他们小组的意思每次数字大变动,都会有很大的改变opencv3的alpha版本介绍说是重新定义了API,而且在CPU上进行了效果提升,在GPU上可以透明加速,也就是你在编程的时候不知道是在GPU上)。

看了下opencv3自带的tutorial,大部分也都还是差不多。本文是想将那些零散的例子能够更加的脱水,就是只说其中的几个精髓的函数。这是core部分的例子,虽然之前所有例子都码了一遍,不过觉得暂时自己用不到 【离散傅里叶变换】,所以这里没放进来,有兴趣的可以自己去看看。

正文

一、计时函数

需要头文件#include"opencv2/core/core.hpp"

double t = (double)getTickCount();
// 做点什么 ...
t = 1000*((double)getTickCount() - t)/getTickFrequency();
cout << "Times passed in seconds: " << t << endl;
计时函数就和matlab中的tic ,toc一样,可以用来计算你的代码跑了多久,其实如果不是为了衡量效率什么的,这也是不怎么会用吧,首先进行提取当前电脑的时钟数,这时候返回的是基于上次某个事件(比如开机)开始计算的时钟数;

然后经过了一些操作,接着再次提取当前的时钟数并减去之前的数值,这时候是时钟数,需要除以时钟频率得到时间计数,记得这里是毫秒为单位,所以不要忘记乘以1000来表示多少秒。

二、矩阵掩码

需要头文件#include"opencv2/imgproc/imgproc.hpp"    

这里的例子说的就是针对一个矩阵进行卷积,如果有过卷积神经网络(CNN)背景的就知道,通过对一副图像进行卷积然后得到另一个卷积后的图像。OpenCV中实现这一想法的就是filter2D过滤器,不过它是基于图像的,不像是matlab是基于完全的矩阵,所以当输入的是彩色图像的时候,它是在三个通道上独立的运行的:也就是对BGR三个通道分成三个矩阵,每个矩阵独立进行卷积,然后接着三个矩阵再次合并成一个新的图像。

Mat kern = (Mat_<char>(3,3) <<  0, -1,  0,
                               -1,  5, -1,
                                0, -1,  0);
filter2D(I, K, I.depth(), kern );
imshow("your window's name",K);
上面第一行是先创建一个2D过滤器,在CNN中也叫做卷积核,机器学习中也叫做特征提取器。这里的创建方法虽然在前面一个博文中未介绍,不过觉得这个很像是特地为filter2D函数设计的,所以放在这里说,这是个运算符重定义,先Mat_<char>(3,3)先建立个3×3大小的矩阵,然后接着使用重定义操作符《来进行初始化,将得到的结果在赋值给核kern。

void filter2D(InputArray src, OutputArray dst, int ddepth, InputArray kernel, Point anchor=Point(-1,-1), double delta=0, int borderType=BORDER_DEFAULT )

filter2D的参数列表:输入图像,输出图像,输入图像的深度,卷积核,指定核的中心,卷积过程中加到每个像素上的值,指定在未定义区域上的行为。后两个参数是可选参数,也就是有默认形参的。

depth()表示的是位深度,就是每个元素是多少位的,是8位还是16位。

refman中第246页。在filter2D中如果第三个参数是-1,那么输出的深度就和输入是一样的。

这个函数在图像上应用的是一个线性过滤器。支持in-place操作。当这个滑框部分超出了图像的时候,这个函数会按照具体的边界模式来插补外部的像素值。这个函数实际上是计算相关性,而不是卷积:


也就是说,这个kernel不是围绕着锚点镜像的。如果真的需要一个卷积,可以使用flip()来操作,然后设置新的锚点:(kernel.cols - anchor.x - 1, kernel.rows - anchor.y - 1) 。

这个函数是使用基于DFT的算法来应对大kernel(11×11或者更大)的,并且使用直接的算法(通过函数createLinearFIlter()实现的)来应对小kernel。

三、图像叠加

就是将两幅图进行不同程度的叠加,公式为:


这里g(x)为输出图像,f(x)为对应的两幅图像,其实我觉得可以多福图像叠加,虽然没什么意义,不过原理上应该说的通。

beta = ( 1.0 - alpha );
addWeighted( src1, alpha, src2, beta, 0.0, dst);
imshow("your window's name",dst);
addWeighted函数就是将两个源图像加到一起,而且也是理解成每个对应通道对应相加。这里特别注意的是两个图像要一样大小,所以在读取不一样大小的可以通过设定不同的ROI或者对图像进行缩放来完成这个目的。


四、防止数值溢出


上面的式子就是针对一副图像进行图像对比度和亮度的调整,因为i是在一副图像上增加数值,如果BGR都增加到最大,那么就呈白色了,也就是增加每个通道上的亮度。对于这个操作,会有一定的几率出现结果超出255,那么就不能算是正常的值了,所以需要对结果进行限定,

for( size_t y = 0; y < image.rows; y++ )
{
    for( size_t x = 0; x < image.cols; x++ )
    {
        for( size_t c = 0; c < 3; c++ )
        {
            new_image.at<Vec3b>(y,x)[c] = saturate_cast<uchar>( alpha*( image.at<Vec3b>(y,x)[c] ) + beta );
        }
    }
}
image.convertTo(new_image, -1, alpha, beta);
上面的saturate_cast<uchar>()函数就是针对参数进行限定,乍一看还以为是cpp自带的类似static_castn那种,其实这个是opencv小组写的,

template<typename _Tp> static inline _Tp saturate_cast(uchar v) { return _Tp(v); }
template<typename _Tp> static inline _Tp saturate_cast(schar v) { return _Tp(v); }
template<typename _Tp> static inline _Tp saturate_cast(ushort v) { return _Tp(v); }
template<typename _Tp> static inline _Tp saturate_cast(short v) { return _Tp(v); }

在<operations.hpp>头文件中,是通过使用cpp语言的截断功能来实现的,就是强制转换,比如一个超出uchar的数值,那么进行uchar的强制转换,直接丢弃超出的部分。

上面的convertTo()函数就是执行式子中的操作,第二个参数就是int rtype,如果是负数,那么输出图像就使用与输入图像一样的类型。

五、xml及yaml文件操作

这部分还是挺重要的,因为opencv中的很多分类器都是放在xml文件中的,而且xml适合web传输,所以这部分还是得会的。

XML和YAML的串行化分别采用两种不同的数据结构: mappings (就像STL map) 和 element sequence (比如 STL vector>。二者之间的区别在map中每个元素都有一个唯一的标识名供用户访问;而在sequences中你必须遍历所有的元素才能找到指定元素。

1、打开和关闭

string filename = "I.xml";
FileStorage fs(filename, FileStorage::WRITE);
\\...
fs.open(filename, FileStorage::READ);
fs.release();  
上面是进行打开对应的文本进行读写,这里两种方法都可以(即在初始化的时候指定或者调用open函数),opencv针对xml和yaml文本有涉及到两个数据结构:FileStorage和FileNode。

FileStorage:在OpenCV中标识XML和YAML的数据结构是FileStorage 。其中的第二个参数都以常量形式指定你要对文件进行操作的类型,包括:WRITE, READ 或 APPEND。文件扩展名决定了你将采用的输出格式。如果你指定扩展名如 .xml.gz ,输出甚至可以是压缩文件。

FIleNode:对于数据读取,可使用 FileNode 和 FileNodeIterator 数据结构。 FileStorage 的[] 操作符将返回一个 FileNode 数据类型。如果这个节点是序列化的,我们可以使用 FileNodeIterator 来迭代遍历所有元素。

2、普通读写

int itNr;
fs["iterationNr"] >> itNr; //读操作
或者 itNr = (int) fs["iterationNr"]; //读操作
fs << "iterationNr" << 100;//写操作
如上面说的fs[]返回的是FileNode的数据类型,然后调用重载符>>来进行输出其中节点为["iterationNr"]的值到itNr中。

Mat R = Mat_<uchar >::eye  (3, 3),
    T = Mat_<double>::zeros(3, 1);

fs << "R" << R;                                      // 写 cv::Mat
fs << "T" << T;

fs["R"] >> R;                                      // 读 cv::Mat
fs["T"] >> T;
如上面code,通过对fs进行建立“R”和“T”的节点,然后接着写入其数据;下面就是进行搜寻对应节点然后在读取数据。

3、多数据读写

对于序列来说。写入:,在第一个元素前输出”[“字符,并在最后一个元素后输出”]“字符,中间就是所需要自己输入的数据:

fs << "strings" << "[";                              // 文本 - 字符串序列
fs << "image1.jpg" << "Awesomeness" << "baboon.jpg";
fs << "]";                                           // 序列结束

这里的结果就是:

<?xml version="1.0"?>
<opencv_storage>
<iterationNr>100</iterationNr>
<strings>
  image1.jpg Awesomeness baboon.jpg</strings>
如上面结果所示,在节点strings中,是没有顺序可言的,所以只能按照顺序读取然后进行比对结果。

读取:采用FileNode数据结构先索引到对应的节点,然后采用Fileiterator迭代器进行一个一个的索引:

FileNode n = fs["strings"];                         // 读取字符串序列 - 获取节点
if (n.type() != FileNode::SEQ)
{
    cerr << "strings is not a sequence! FAIL" << endl;
    return 1;
}

FileNodeIterator it = n.begin(), it_end = n.end(); // 遍历节点
for (; it != it_end; ++it)
    cout << (string)*it << endl;//这里可以进行所需要的操作,比如对比或者什么的。

对于maps来说写入:与序列不同的在于采用”{“和”}“作为分隔符:

fs << "Mapping";                              // 文本 - mapping
fs << "{" << "One" << 1;
fs <<        "Two" << 2 << "}";
如上图,先建立个Mapping节点,但是后面的“{”告诉fs,将要采用maps的方式进行写入,所以这里前面多了两个可以索引的“one”和“two”,然后接着输入数据。

读取

n = fs["Mapping"];                                // 从序列中读取map
cout << "Two  " << (int)(n["Two"]) << "; ";
cout << "One  " << (int)(n["One"]) << endl << endl;
和上面一样先使用FileNode,找到节点,然后直接进行n["One"]的索引,这里返回值应该也是FileNode类型,然后转换成自己需要的类型就好。

如果要自定义数据类型,比如自定义类来包含一些数据和操作,为了便捷,记得重载<<,>>操作符。

更详细的部分请参考:http://www.opencv.org.cn/opencvdoc/2.3.2/html/doc/tutorials/core/file_input_output_with_xml_yml/file_input_output_with_xml_yml.html#fileinputoutputxmlyaml

六、基本绘图

这里介绍文本的打印和图形的绘制,首先介绍一个也许会用到的RNG类,然后介绍在图像上如何输出文字和基本的线啊,矩形啊,圆形啊什么的。

RNG rng( 0xFFFFFFFF );//或者直接RNG rng;
 int x = (int)rng.uniform( a, b);
第一行是建立个RNG的对象,然后进行初始化种子(可有可无,如果每次的种子都是相同的,那么结果就有比较性,所以matlab中都需要rand('state',0)在代码的开始);

第二个是随机提取个值,这个值是介于【a,b)之间的:

inline int RNG::uniform(int a, int b) { return a == b ? a : (int)(next()%(b - a) + a); }
inline float RNG::uniform(float a, float b) { return ((float)*this)*(b - a) + a; }
inline double RNG::uniform(double a, double b) { return ((double)*this)*(b - a) + a; }

可以看出,它的返回值就三个,所以如果不是这三个就需要进行强制转换了,这三行代码在<operations.hpp>中。

文本

putText( image, "Testing text rendering", org, rng.uniform(0,8),
         rng.uniform(0,100)*0.05+0.1, randomColor(rng), rng.uniform(1, 10), lineType);
putText()函数的参数列表:所打印的位置的图像,打印的文字,文字左下角的坐标,文字的字体的参数,文字的缩放,颜色,字体粗细程度,线的类型。

其实后面还有个可选的参数,这里说下lineType,在《refman》中的line()函数中具体介绍了,有三种形式分别为8、4、CV_AA。这三种。

Size textsize = getTextSize("OpenCV forever!", CV_FONT_HERSHEY_COMPLEX, 3, 5, 0);
  Point org((window_width - textsize.width)/2, (window_height - textsize.height)/2);
上面第一行就是提取文字的尺寸,采用getTextSize()函数

getTextSize()函数的参数列表:输入的文本字符串,fortFace(详见refman中的putText()函数部分的解释),字体的缩放大小(祥见第二个参数部分),同前两个参数,这是个指针表示相对于最底部的文本的点上y坐标的输出参数。

第二行就是建立文字需要摆放的左下角的坐标,这里的window_width和window_height是所被打印的图像的宽和高。

基本图形

文字可以用来做图像的标记,图形可以用来框出感兴趣的区域。

线:void line(InputOutputArray img, Point pt1, Point pt2, const Scalar& color, int thickness=1, int lineType=LINE_8, int shift=0 );

参数列表:被打印的图像,起始点,终点,颜色,线粗细,线类型,平移。

int Drawing_Random_Lines( Mat image, char* window_name, RNG rng )
{
  Point pt1, pt2;
  for( int i = 0; i < NUMBER; i++ )
  {
    pt1.x = rng.uniform( x_1, x_2 );
    pt1.y = rng.uniform( y_1, y_2 );
    pt2.x = rng.uniform( x_1, x_2 );
    pt2.y = rng.uniform( y_1, y_2 );
    line( image, pt1, pt2, randomColor(rng), rng.uniform(1, 10), 8 );
    imshow( window_name, image );
    if( waitKey( DELAY ) >= 0 )
      { return -1; }
  }
  return 0;
}

待续。。

http://www.opencv.org.cn/opencvdoc/2.3.2/html/doc/tutorials/core/basic_geometric_drawing/basic_geometric_drawing.html#drawing-1



posted @ 2014-12-17 20:00  仙守  阅读(3006)  评论(0编辑  收藏  举报