• 从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.imreadfilename )
    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.shapearray.dtype
    (, (393, 512, 3), dtype('uint8'))
    >>> iplimage

    >>> cvmat

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

    在数组、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,:]))


    >>> cv.GetImage(cv.fromarray(array[::2,::2,:].copy()))

    在《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 PyObjectpyopencv_line(PyObjectPyObjectargsPyObjectkw)
    {
        PyObjectpyobj_img NULL;
        Mat img;
        PyObjectpyobj_pt1 NULL;
        Point pt1;
        PyObjectpyobj_pt2 NULL;
        Point pt2;
        PyObjectpyobj_color NULL;
        Scalar color;
        int thickness=1;
        int lineType=8;
        int shift=0;

        const charkeywords[] "img""pt1""pt2""color""thickness","lineType""shift"NULL };
        ifPyArg_ParseTupleAndKeywords(argskw"OOOO|iii:line"(char**)keywords,&pyobj_img&pyobj_pt1,
            &pyobj_pt2&pyobj_color&thickness&lineType&shift&&
            pyopencv_to(pyobj_imgimg&&
            pyopencv_to(pyobj_pt1pt1&&
            pyopencv_to(pyobj_pt2pt2&&
            pyopencv_to(pyobj_colorcolor)
        {
            ERRWRAP2cv::line(imgpt1pt2colorthicknesslineTypeshift));
            Py_RETURN_NONE;
        }

        return NULL;
    }

    OpenCV 中的line()所需的4个参数类型为:Mat、Point、Point和Scalar,程序中使用4个pyopencv_to()将Python的数据 转换为这些类型。pyopencv_to()有众多重载函数,例如上述的类型转换实际上会调用如下三个函数:

    static int pyopencv_to(const PyObjectoMatmconst charname ""bool allowND=true);
    static inline bool pyopencv_to(PyObjectobjPointpconst charname "");
    static bool pyopencv_to(PyObject *oScalarsconst char *name "");

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

    static inline bool pyopencv_to(PyObjectobjPointpconst charname "")
    {
        if(!obj || obj == Py_None)
            return true;
        if(PyComplex_CheckExact(obj))
        {
            Py_complex PyComplex_AsCComplex(obj);
            p.saturate_cast<int>(c.real);
            p.saturate_cast<int>(c.imag);
            return true;
        }
        return PyArg_ParseTuple(obj"ii"&p.x&p.y0;
    }

    稍微分析一下此程序可知,它可以将Python的复数和元组转换为Point对象。例如100+200j或者(100,200)。

    Scalar对应的函数为:

    static bool pyopencv_to(PyObject *oScalarsconst char *name "")
    {
        if(!|| == Py_None)
            return true;
        if (PySequence_Check(o)) {
            PyObject *fi PySequence_Fast(oname);
            if (fi == NULL)
                return false;
            if (PySequence_Fast_GET_SIZE(fi))
            {
                failmsg("Scalar value for argument '%s' is longer than 4"name);
                return false;
            }
            for (Py_ssize_t 0PySequence_Fast_GET_SIZE(fi); i++{
                PyObject *item PySequence_Fast_GET_ITEM(fii);
                if (PyFloat_Check(item|| PyInt_Check(item)) {
                    s[(int)iPyFloat_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[0PyFloat_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::calcHistInputArrayOfArrays imagesconst vector<int>& channels,
                 InputArray maskOutputArray 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<ucharvector_uchar;
    typedef vector<intvector_int;
    typedef vector<floatvector_float;
    typedef vector<doublevector_double;
    typedef vector<Pointvector_Point;
    typedef vector<Point2fvector_Point2f;
    typedef vector<Vec2fvector_Vec2f;
    typedef vector<Vec3fvector_Vec3f;
    typedef vector<Vec4fvector_Vec4f;
    typedef vector<Vec6fvector_Vec6f;
    typedef vector<Vec4ivector_Vec4i;
    typedef vector<Rectvector_Rect;
    typedef vector<KeyPointvector_KeyPoint;
    typedef vector<Matvector_Mat;
    typedef vector<vector<Pointvector_vector_Point;
    typedef vector<vector<Point2fvector_vector_Point2f;
    typedef vector<vector<Point3fvector_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 (3020),
                         ranges (02560256))

    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中的数据的,请参考其源程序。

  • 相关阅读:
    基于接口的动态代理和基于子类的动态代理
    JDBC连接数据库
    关于使用Binlog和canal来对MySQL的数据写入进行监控
    使用VMware12在CentOS7上部署docker实例
    VMWare12pro安装Centos 6.9教程
    读《Java并发编程的艺术》学习笔记(十)
    读《Java并发编程的艺术》学习笔记(九)
    读《Java并发编程的艺术》学习笔记(八)
    读《Java并发编程的艺术》学习笔记(七)
    读《Java并发编程的艺术》学习笔记(六)
  • 原文地址:https://www.cnblogs.com/timssd/p/4706270.html
Copyright © 2020-2023  润新知