cv2中的函数名与PyOpenCV的相同,部分常量名有所不同。但是cv2中的函数所需的参数类型尽量使用数组或者一些Python的标准数据类型。因此cv2中没有Mat、Point、Size、Vec等各种数据类型,而是用列表、元组或数组表示这些数据类型。因此使用cv2中的函数比PyOpenCV更加便捷,然而你需要清楚cv2的数据类型转换规则,这样才能将正确的数据专递给函数。
分析cv2的源程序
为了了解cv2所做的数据转换工作,需要我们分析cv2的源程序。下载OpenCV的源程序,解压之后,可以在“opencvmodulespythonsrc2”路径下找到cv2相关的源程序。cv2中各个包装函数是通过cv2.py自动生成的:在命令行中切到“src2目录下,并运行命令“cv2.py . ”,将在当前目录下生成OpenCV的包装函数。如果执行提示失败,可在此目录下创建一个空的“opencv_extra_api.hpp”文件之后再试。
所有的包装函数都在自动生成的“pyopencv_generated_funcs.h”中定义。而这些包装函数会调用“cv2.cpp”中的众多pyopencv_to()和pyopencv_from()函数,实现Python和OpenCV的各种类型转换工作。若不能确定包装函数使用何种Python数据类型,可以查看包装函数的内容,例如下面是运行cv2.line()时所调用的C语言函数。
pyopencv_generated_funcs.h, cv2.cpp
在这两个文件中定义了cv2的包装函数和各种类型转换函数
static PyObject * pyopencv_line ( PyObject * , PyObject * args , PyObject * kw )
{
PyObject * pyobj_img = NULL ;
Mat img ;
PyObject * pyobj_pt1 = NULL ;
Point pt1 ;
PyObject * pyobj_pt2 = NULL ;
Point pt2 ;
PyObject * pyobj_color = NULL ;
Scalar color ;
int thickness = 1 ;
int lineType = 8 ;
int shift = 0 ;
const char * keywords [] = { "img" , "pt1" , "pt2" , "color" , "thickness" , "lineType" , "shift" , NULL };
if ( PyArg_ParseTupleAndKeywords ( args , kw , "OOOO|iii:line" , ( char ** ) keywords , & pyobj_img , & pyobj_pt1 ,
& pyobj_pt2 , & pyobj_color , & thickness , & lineType , & shift ) &&
pyopencv_to ( pyobj_img , img ) &&
pyopencv_to ( pyobj_pt1 , pt1 ) &&
pyopencv_to ( pyobj_pt2 , pt2 ) &&
pyopencv_to ( pyobj_color , color ) )
{
ERRWRAP2 ( cv :: line ( img , pt1 , pt2 , color , thickness , lineType , shift ));
Py_RETURN_NONE ;
}
return NULL ;
}
OpenCV中的line()所需的4个参数类型为:Mat、Point、Point和Scalar,程序中使用4个pyopencv_to()将Python的数据转换为这些类型。pyopencv_to()有众多重载函数,例如上述的类型转换实际上会调用如下三个函数:
static int pyopencv_to ( const PyObject * o , Mat & m , const char * name = "" , bool allowND = true );
static inline bool pyopencv_to ( PyObject * obj , Point & p , const char * name = "" );
static bool pyopencv_to ( PyObject * o , Scalar & s , const char * name = "" );
其中Mat对应的pyopencv_to()将数组转换为Mat对象,其代码实现比较复杂,暂时忽略。我们看看Point的转换函数:
static inline bool pyopencv_to ( PyObject * obj , Point & p , const char * name = "" )
{
if ( ! obj || obj == Py_None )
return true ;
if ( PyComplex_CheckExact ( obj ))
{
Py_complex c = PyComplex_AsCComplex ( obj );
p . x = saturate_cast < int > ( c . real );
p . y = saturate_cast < int > ( c . imag );
return true ;
}
return PyArg_ParseTuple ( obj , "ii" , & p . x , & p . y ) > 0 ;
}
稍微分析一下此程序可知,它可以将Python的复数和元组转换为Point对象。例如100+200j或者(100,200)。
Scalar对应的函数为:
static bool pyopencv_to ( PyObject * o , Scalar & s , const char * name = "" )
{
if ( ! o || o == Py_None )
return true ;
if ( PySequence_Check ( o )) {
PyObject * fi = PySequence_Fast ( o , name );
if ( fi == NULL )
return false ;
if ( 4 < PySequence_Fast_GET_SIZE ( fi ))
{
failmsg ( "Scalar value for argument '%s' is longer than 4" , name );
return false ;
}
for ( Py_ssize_t i = 0 ; i < PySequence_Fast_GET_SIZE ( fi ); i ++ ) {
PyObject * item = PySequence_Fast_GET_ITEM ( fi , i );
if ( PyFloat_Check ( item ) || PyInt_Check ( item )) {
s [( int ) i ] = PyFloat_AsDouble ( item );
} else {
failmsg ( "Scalar value for argument '%s' is not numeric" , name );
return false ;
}
}
Py_DECREF ( fi );
} else {
if ( PyFloat_Check ( o ) || PyInt_Check ( o )) {
s [ 0 ] = PyFloat_AsDouble ( o );
} else {
failmsg ( "Scalar value for argument '%s' is not numeric" , name );
return false ;
}
}
return true ;
}
可以看出这个函数能将长度小于等于4的序列,整数、浮点数转换为Scalar类型。对于整数和浮点数,它将保存进Scalar对象的第0个元素。
如果读者不清楚某个函数所需的参数类型,可以仿照上述方法从“pyopencv_generated_funcs.h”中的包装函数和对应的pyopencv_to()转换函数找到答案。
在PyOpenCV中为了保存处理结果,我们需要创建一个空的Mat对象,并将其传递给处理函数。处理函数会为此Mat对象添加处理结果。在cv2中一切变得简单了,处理结果可以通过函数的返回值获得。如果需要让处理结果保存到指定的数组之中,也可以将数组传递给dst参数。下面看一个例子,我们希望调用blur()对图像进行模糊处理,从blur()的文档我们可以看到如下参数调用说明:
http://opencv.itseez.com/modules/imgproc/doc/filtering.html?highlight=blur#cv2.blur
blur()的说明文档
C++:
blur(InputArray src, OutputArray dst, Size ksize,
Point anchor=Point(-1,-1), int borderType=BORDER_DEFAULT )
Python:
cv2.blur(src, ksize[, dst[, anchor[, borderType]]]) → dst
可以看到在C++中ksize是一个Size对象,Size和Point类似,因此Python中只需要传递一个元组即可。dst参数是可选参数,下面我们用代码测试一下:
>>> img = cv2 . imread ( "lena.jpg" )
>>>
>>> img2 = cv2 . blur ( img , ( 5 , 5 ))
>>>
>>> img3 = np . empty_like ( img ) # 先分配一个相同大小的数组
>>> img4 = cv2 . blur ( img , ( 5 , 5 ), dst = img3 ) # 然后通过dst参数指定保存结果数组
>>>
>>> np . all ( img2 == img3 )
True
>>> img3 is img4 # 当指定dst参数时,返回值和dst参数是同一个对象
True
常用的类型转换
下面列出一些我在将书中的实例程序移植到cv2下时总结的类型转换,读者可以参照本节的内容分析移植之后的程序。
下面通过几个例子说明参数的传递方法。
在PyOpenCV的实例中,计算直方图统计的calcHist()参数十分复杂,而由于cv2的自动类型转换功能,calcHist()的用法变得简单多了。cv2中calcHist()的帮助文档如下:
calcHist(images, channels, mask, histSize, ranges[, hist[, accumulate]]) -> hist
由于在C++中,同样的函数名可以对应多种参数的函数实现,因此我们需要确定cv2中所调用的C++函数类型,下面是“pyopencv_generated_funcs.h”中对calcHist()进行包装的函数。
static PyObject* pyopencv_calcHist(PyObject* , PyObject* args, PyObject* kw)
{
PyObject* pyobj_images = NULL;
vector_Mat images;
PyObject* pyobj_channels = NULL;
vector_int channels;
PyObject* pyobj_mask = NULL;
Mat mask;
PyObject* pyobj_hist = NULL;
Mat hist;
PyObject* pyobj_histSize = NULL;
vector_int histSize;
PyObject* pyobj_ranges = NULL;
vector_float ranges;
bool accumulate=false;
...
}
通过这些参数类型,可以在OpenCV源程序中找到其对应的C++函数:
void cv :: calcHist ( InputArrayOfArrays images , const vector < int >& channels ,
InputArray mask , OutputArray hist ,
const vector < int >& histSize ,
const vector < float >& ranges ,
bool accumulate );
可以看出images参数对应vector_Mat类型、channels参数vector_int类型、mask对应Mat类型、histSize对应vector_int类型、ranges对应vector_float类型。而可选hist参数则用来指定输出结果的数组。
这些vector_*类型在“cv2.cpp”中定义:
typedef vector < uchar > vector_uchar ;
typedef vector < int > vector_int ;
typedef vector < float > vector_float ;
typedef vector < double > vector_double ;
typedef vector < Point > vector_Point ;
typedef vector < Point2f > vector_Point2f ;
typedef vector < Vec2f > vector_Vec2f ;
typedef vector < Vec3f > vector_Vec3f ;
typedef vector < Vec4f > vector_Vec4f ;
typedef vector < Vec6f > vector_Vec6f ;
typedef vector < Vec4i > vector_Vec4i ;
typedef vector < Rect > vector_Rect ;
typedef vector < KeyPoint > vector_KeyPoint ;
typedef vector < Mat > vector_Mat ;
typedef vector < vector < Point > > vector_vector_Point ;
typedef vector < vector < Point2f > > vector_vector_Point2f ;
typedef vector < vector < Point3f > > vector_vector_Point3f ;
由此可知这些都是vector类型,可以通过序列对象指定参数,下面是调用calcHist()的实例程序,其中使用了列表序列和元组序列:
import cv2
import numpy as np
img = cv2 . imread ( "lena.jpg" )
result = cv2 . calcHist ([ img ],
channels = ( 0 , 1 ),
mask = None ,
histSize = ( 30 , 20 ),
ranges = ( 0 , 256 , 0 , 256 ))
hist , _x , _y = np . histogram2d ( img [:,:, 0 ] . flatten (), img [:,:, 1 ] . flatten (),
bins = ( 30 , 20 ), range = [( 0 , 256 ),( 0 , 256 )])
print np . all ( hist == result )
请注意由于ranges对应vector类型,因此和np.histogram2d()不同,不用指定多层嵌套的数据结构。至于calcHist()的C++程序是如何使用ranges中的数据的,请参考其源程序。