从PyOpenCV到CV2

安装cv2

http://hyry.dip.jp/files/opencv.zip

采用cv2重写的《Python科学计算》中的实例程序

读者可以在下面的页面中搜索“opencv”,并根据Python版本下载对应的安装程序。

http://www.lfd.uci.edu/~gohlke/pythonlibs/

非官方的Windows系统Python扩展库

安装完毕之后,运行下面的程序,测试是否安装正确。

import  cv2
import  sys

try :
     filename  =  sys . argv [ 1 ]
except :
     filename  =  "lena.jpg"
img  =  cv2 . imread (  filename  )
cv2 . namedWindow ( "demo1" )
cv2 . imshow ( "demo1" ,  img )
cv2 . waitKey ( 0 )
cv和cv2

本章所介绍的代码均采用如下载入方式:

>>>  import  cv2
>>>  from  cv2  import  cv

cv2扩展库是针对OpenCV 2.x API创建的,它直接采用NumPy的数组对象表示图像,因此和PyOpenCV相比,不再需要在数组和Mat对象之间相互转换了。

为了兼容OpenCV 1.x API,在cv下提供了原来的OpenCV 1.x API的扩展库。如果读者发现cv2下缺少某个功能,可以使用cv下提供的函数。

cv2下的函数直接对NumPy的数组进行操作,而cv则对两种表示图像的cvmat和iplimage对象进行操作。如果需要混用这两套API中的函数,就需要在它们之间进行转换,下面让我们看一个在这些类型之间转换的例子。

>>>  array  =  cv2 . imread ( "lena.jpg" )
>>>  iplimage  =  cv . LoadImage ( "lena.jpg" )
>>>  cvmat  =  cv . LoadImageM ( "lena.jpg" )

首先通过cv2.imread()读入的图像使用NumPy数组表示,而通过cv.LoadImage()读入的图像为iplimage对象,通过cv.LoadImage()读入的是cvmat对象。

>>>  type ( array ),  array . shape ,  array . dtype
(<type 'numpy.ndarray'>, (393, 512, 3), dtype('uint8'))
>>>  iplimage
<iplimage(nChannels=3 width=512 height=393 widthStep=1536 )>
>>>  cvmat
<cvmat(type=42424010 8UC3 rows=393 cols=512 step=1536 )>

下表列出了在这三种对象之间转换的方法:

在数组、iplimage以及cvmat之间转换
类型转换 方法
array→cvmat cv.fromarray(array)
cvmat→array np.asarray(cvmat)
cvmat→iplimage cv.GetImage(cvmat)
iplimage→cvmat iplimage[:],或cv.GetMat(iplimage)

如果需要在array和iplimage之间转换,可以通过cvmat作为桥梁,例如:

>>>  array2  =  np . asarray ( iplimage [:])
>>>  np . all ( array  ==  array2 )
True

由于iplimage类型需要其数据保存在连续的内存空间之中,因此使用切片获得的数组需要复制之后才能转换为iplimage:

>>>  cv . GetImage ( cv . fromarray ( array [:: 2 ,:: 2 ,:]))
<ERROR: expected a single-segment buffer object>
<iplimage(nChannels=3 width=256 height=197 widthStep=3072 )>
>>>  cv . GetImage ( cv . fromarray ( array [:: 2 ,:: 2 ,:] . copy ()))
<iplimage(nChannels=3 width=256 height=197 widthStep=768 )>

在《Python科学计算》的OpenCV实例所用到的函数中,只有pyrSegmentation()不在cv2中,因此使用了cv下的PyrSegmentation(),并在恰当的地方进行图像类型的转换。

opencv_pyrSegmentation.py

使用cv.PyrSegmentation()进行图像分割

由于OpenCV 1.x API已经逐渐被淘汰,后续的章节将只详细介绍cv2的使用方法。

cv2与PyOpenCV

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  =  "<unknown>" ,  bool  allowND = true );
static  inline  bool  pyopencv_to ( PyObject *  obj ,  Point &  p ,  const  char *  name  =  "<unknown>" );
static  bool  pyopencv_to ( PyObject  * o ,  Scalar &  s ,  const  char  * name  =  "<unknown>" );

其中Mat对应的pyopencv_to()将数组转换为Mat对象,其代码实现比较复杂,暂时忽略。我们看看Point的转换函数:

static  inline  bool  pyopencv_to ( PyObject *  obj ,  Point &  p ,  const  char *  name  =  "<unknown>" )
{
     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  =  "<unknown>" )
{
     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()的文档我们可以看到如下参数调用说明:

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<Type>类型,可以通过序列对象指定参数,下面是调用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<float>类型,因此和np.histogram2d()不同,不用指定多层嵌套的数据结构。至于calcHist()的C++程序是如何使用ranges中的数据的,请参考其源程序。

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值