Compiling OpenCV and python wrapper

Hello,

I try to upgrade the following repo to Opencv 4:

I also use the repo that inspired the previous repo to upgrade to the opencv4.

Here is my files:pyboostcvconverter.hpp

/*
 * CVBoostConverter.hpp
 *
 *  Created on: Mar 20, 2014
 *      Author: Gregory Kramida
 *   Copyright: (c) 2014 Gregory Kramida
 *     License: MIT
 */

#ifndef CVBOOSTCONVERTER_HPP_
#define CVBOOSTCONVERTER_HPP_
#define PY_ARRAY_UNIQUE_SYMBOL helloWorld_ARRAY_API

#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION
#include <Python.h>
#include <boost/python.hpp>
#include <cstdio>
#include <numpy/ndarrayobject.h>
#include <opencv2/core/core.hpp>

#if (PY_VERSION_HEX >= 0x03000000)
#ifndef NUMPY_IMPORT_ARRAY_RETVAL
#define NUMPY_IMPORT_ARRAY_RETVAL NULL
#endif
#else
#ifndef NUMPY_IMPORT_ARRAY_RETVAL
#define NUMPY_IMPORT_ARRAY_RETVAL
#endif
#endif

#define BOOST_BIND_GLOBAL_PLACEHOLDERS

namespace pbcvt {

using namespace cv;

static PyObject *opencv_error = 0;

//===================    MACROS
//=================================================================
#define ERRWRAP2(expr)                                                         \
  try {                                                                        \
    PyAllowThreads allowThreads;                                               \
    expr;                                                                      \
  } catch (const cv::Exception &e) {                                           \
    PyErr_SetString(opencv_error, e.what());                                   \
    return 0;                                                                  \
  }

//===================   ERROR HANDLING
//=========================================================

static int failmsg(const char *fmt, ...);

//===================   THREADING
//==============================================================
class PyAllowThreads;
class PyEnsureGIL;

static size_t REFCOUNT_OFFSET =
    (size_t) &
    (((PyObject *)0)->ob_refcnt) +
        (0x12345678 != *(const size_t *)"\x78\x56\x34\x12\0\0\0\0\0") *
            sizeof(int);

static inline PyObject *pyObjectFromRefcount(const int *refcount) {
  return (PyObject *)((size_t)refcount - REFCOUNT_OFFSET);
}

static inline int *refcountFromPyObject(const PyObject *obj) {
  return (int *)((size_t)obj + REFCOUNT_OFFSET);
}

//===================   NUMPY ALLOCATOR FOR OPENCV
//=============================================

class NumpyAllocator;

//===================   STANDALONE CONVERTER FUNCTIONS
//=========================================

PyObject *fromMatToNDArray(const Mat &m);
Mat fromNDArrayToMat(PyObject *o);

//===================   BOOST CONVERTERS
//=======================================================

struct matToNDArrayBoostConverter {
  static PyObject *convert(Mat const &m);
};

struct matFromNDArrayBoostConverter {

  matFromNDArrayBoostConverter();

  /// @brief Check if PyObject is an array and can be converted to OpenCV
  /// matrix.
  static void *convertible(PyObject *object);

  /// @brief Construct a Mat from an NDArray object.
  static void
  construct(PyObject *object,

            boost::python::converter::rvalue_from_python_stage1_data *data);
};
} // end namespace pbcvt
#endif /* CVBOOSTCONVERTER_HPP_ */

pyboost_cv3_converter.cpp:

/*
 * CV4BoostConverter.cpp
 *
 */
#define NO_IMPORT_ARRAY
#define PY_ARRAY_UNIQUE_SYMBOL helloWorld_ARRAY_API
#include "/home/xavier/dev/OpenCV-Python-C-Module-for-Image-Processing/pyboostcvconverter/pyboostcvconverter.hpp"
#if !defined CV_VERSION_EPOCH && CV_VERSION_MAJOR == 4
namespace pbcvt {
using namespace cv;
//===================   ERROR HANDLING
//=========================================================

static int failmsg(const char *fmt, ...) {
  char str[1000];

  va_list ap;
  va_start(ap, fmt);
  vsnprintf(str, sizeof(str), fmt, ap);
  va_end(ap);

  PyErr_SetString(PyExc_TypeError, str);
  return 0;
}

//===================   THREADING
//==============================================================
class PyAllowThreads {
public:
  PyAllowThreads() : _state(PyEval_SaveThread()) {}
  ~PyAllowThreads() { PyEval_RestoreThread(_state); }

private:
  PyThreadState *_state;
};

class PyEnsureGIL {
public:
  PyEnsureGIL() : _state(PyGILState_Ensure()) {}
  ~PyEnsureGIL() { PyGILState_Release(_state); }

private:
  PyGILState_STATE _state;
};

enum { ARG_NONE = 0, ARG_MAT = 1, ARG_SCALAR = 2 };

class NumpyAllocator : public MatAllocator {
public:
  NumpyAllocator() { stdAllocator = Mat::getStdAllocator(); }
  ~NumpyAllocator() {}

  UMatData *allocate(PyObject *o, int dims, const int *sizes, int type,
                     size_t *step) const {
    UMatData *u = new UMatData(this);
    u->data = u->origdata = (uchar *)PyArray_DATA((PyArrayObject *)o);
    npy_intp *_strides = PyArray_STRIDES((PyArrayObject *)o);
    for (int i = 0; i < dims - 1; i++)
      step[i] = (size_t)_strides[i];
    step[dims - 1] = CV_ELEM_SIZE(type);
    u->size = sizes[0] * step[0];
    u->userdata = o;
    return u;
  }

  UMatData *allocate(int dims0, const int *sizes, int type, void *data,
                     size_t *step, cv::AccessFlag flags,
                     UMatUsageFlags usageFlags) const {
    if (data != 0) {
      CV_Error(Error::StsAssert, "The data should normally be NULL!");
      // probably this is safe to do in such extreme case
      return stdAllocator->allocate(dims0, sizes, type, data, step, flags,
                                    usageFlags);
    }
    PyEnsureGIL gil;

    int depth = CV_MAT_DEPTH(type);
    int cn = CV_MAT_CN(type);
    const int f = (int)(sizeof(size_t) / 8);
    int typenum = depth == CV_8U    ? NPY_UBYTE
                  : depth == CV_8S  ? NPY_BYTE
                  : depth == CV_16U ? NPY_USHORT
                  : depth == CV_16S ? NPY_SHORT
                  : depth == CV_32S ? NPY_INT
                  : depth == CV_32F ? NPY_FLOAT
                  : depth == CV_64F ? NPY_DOUBLE
                                    : f * NPY_ULONGLONG + (f ^ 1) * NPY_UINT;
    int i, dims = dims0;
    cv::AutoBuffer<npy_intp> _sizes(dims + 1);
    for (i = 0; i < dims; i++)
      _sizes[i] = sizes[i];
    if (cn > 1)
      _sizes[dims++] = cn;
    PyObject *o = PyArray_SimpleNew(dims, _sizes, typenum);
    if (!o)
      CV_Error_(Error::StsError,
                ("The numpy array of typenum=%d, ndims=%d can not be created",
                 typenum, dims));
    return allocate(o, dims0, sizes, type, step);
  }

  bool allocate(UMatData *u, cv::AccessFlag accessFlags,
                UMatUsageFlags usageFlags) const {
    return stdAllocator->allocate(u, accessFlags, usageFlags);
  }

  void deallocate(UMatData *u) const {
    if (u) {
      PyEnsureGIL gil;
      PyObject *o = (PyObject *)u->userdata;
      Py_XDECREF(o);
      delete u;
    }
  }

  const MatAllocator *stdAllocator;
};

//===================   ALLOCATOR INITIALIZTION
//==================================================
NumpyAllocator g_numpyAllocator;

//===================   STANDALONE CONVERTER FUNCTIONS
//=========================================

PyObject *fromMatToNDArray(const Mat &m) {
  if (!m.data)
    Py_RETURN_NONE;
  Mat temp, *p = (Mat *)&m;
  if (!p->u || p->allocator != &g_numpyAllocator) {
    temp.allocator = &g_numpyAllocator;
    ERRWRAP2(m.copyTo(temp));
    p = &temp;
  }
  PyObject *o = (PyObject *)p->u->userdata;
  Py_INCREF(o);
  return o;
}

Mat fromNDArrayToMat(PyObject *o) {
  cv::Mat m;
  bool allowND = true;
  if (!PyArray_Check(o)) {
    failmsg("argument is not a numpy array");
    if (!m.data)
      m.allocator = &g_numpyAllocator;
  } else {
    PyArrayObject *oarr = (PyArrayObject *)o;

    bool needcopy = false, needcast = false;
    int typenum = PyArray_TYPE(oarr), new_typenum = typenum;
    int type = typenum == NPY_UBYTE    ? CV_8U
               : typenum == NPY_BYTE   ? CV_8S
               : typenum == NPY_USHORT ? CV_16U
               : typenum == NPY_SHORT  ? CV_16S
               : typenum == NPY_INT    ? CV_32S
               : typenum == NPY_INT32  ? CV_32S
               : typenum == NPY_FLOAT  ? CV_32F
               : typenum == NPY_DOUBLE ? CV_64F
                                       : -1;

    if (type < 0) {
      if (typenum == NPY_INT64 || typenum == NPY_UINT64 || type == NPY_LONG) {
        needcopy = needcast = true;
        new_typenum = NPY_INT;
        type = CV_32S;
      } else {
        failmsg("Argument data type is not supported");
        m.allocator = &g_numpyAllocator;
        return m;
      }
    }

#ifndef CV_MAX_DIM
    const int CV_MAX_DIM = 32;
#endif

    int ndims = PyArray_NDIM(oarr);
    if (ndims >= CV_MAX_DIM) {
      failmsg("Dimensionality of argument is too high");
      if (!m.data)
        m.allocator = &g_numpyAllocator;
      return m;
    }

    int size[CV_MAX_DIM + 1];
    size_t step[CV_MAX_DIM + 1];
    size_t elemsize = CV_ELEM_SIZE1(type);
    const npy_intp *_sizes = PyArray_DIMS(oarr);
    const npy_intp *_strides = PyArray_STRIDES(oarr);
    bool ismultichannel = ndims == 3 && _sizes[2] <= CV_CN_MAX;

    for (int i = ndims - 1; i >= 0 && !needcopy; i--) {
      // these checks handle cases of
      //  a) multi-dimensional (ndims > 2) arrays, as well as simpler 1- and
      //  2-dimensional cases b) transposed arrays, where _strides[] elements go
      //  in non-descending order c) flipped arrays, where some of _strides[]
      //  elements are negative
      if ((i == ndims - 1 && (size_t)_strides[i] != elemsize) ||
          (i < ndims - 1 && _strides[i] < _strides[i + 1]))
        needcopy = true;
    }

    if (ismultichannel && _strides[1] != (npy_intp)elemsize * _sizes[2])
      needcopy = true;

    if (needcopy) {

      if (needcast) {
        o = PyArray_Cast(oarr, new_typenum);
        oarr = (PyArrayObject *)o;
      } else {
        oarr = PyArray_GETCONTIGUOUS(oarr);
        o = (PyObject *)oarr;
      }

      _strides = PyArray_STRIDES(oarr);
    }

    for (int i = 0; i < ndims; i++) {
      size[i] = (int)_sizes[i];
      step[i] = (size_t)_strides[i];
    }

    // handle degenerate case
    if (ndims == 0) {
      size[ndims] = 1;
      step[ndims] = elemsize;
      ndims++;
    }

    if (ismultichannel) {
      ndims--;
      type |= CV_MAKETYPE(0, size[2]);
    }

    if (ndims > 2 && !allowND) {
      failmsg("%s has more than 2 dimensions");
    } else {

      m = Mat(ndims, size, type, PyArray_DATA(oarr), step);
      m.u = g_numpyAllocator.allocate(o, ndims, size, type, step);
      m.addref();

      if (!needcopy) {
        Py_INCREF(o);
      }
    }
    m.allocator = &g_numpyAllocator;
  }
  return m;
}

//===================   BOOST CONVERTERS
//=======================================================

PyObject *matToNDArrayBoostConverter::convert(Mat const &m) {
  if (!m.data)
    Py_RETURN_NONE;
  Mat temp, *p = (Mat *)&m;
  if (!p->u || p->allocator != &g_numpyAllocator) {
    temp.allocator = &g_numpyAllocator;
    ERRWRAP2(m.copyTo(temp));
    p = &temp;
  }
  PyObject *o = (PyObject *)p->u->userdata;
  Py_INCREF(o);
  return o;
}

matFromNDArrayBoostConverter::matFromNDArrayBoostConverter() {
  boost::python::converter::registry::push_back(convertible, construct,
                                                boost::python::type_id<Mat>());
}

/// @brief Check if PyObject is an array and can be converted to OpenCV matrix.
void *matFromNDArrayBoostConverter::convertible(PyObject *object) {
  if (!PyArray_Check(object)) {
    return NULL;
  }
#ifndef CV_MAX_DIM
  const int CV_MAX_DIM = 32;
#endif
  PyArrayObject *oarr = (PyArrayObject *)object;

  int typenum = PyArray_TYPE(oarr);
  if (typenum != NPY_INT64 && typenum != NPY_UINT64 && typenum != NPY_LONG &&
      typenum != NPY_UBYTE && typenum != NPY_BYTE && typenum != NPY_USHORT &&
      typenum != NPY_SHORT && typenum != NPY_INT && typenum != NPY_INT32 &&
      typenum != NPY_FLOAT && typenum != NPY_DOUBLE) {
    return NULL;
  }
  int ndims = PyArray_NDIM(oarr); // data type not supported

  if (ndims >= CV_MAX_DIM) {
    return NULL; // too many dimensions
  }
  return object;
}

/// @brief Construct a Mat from an NDArray object.
void matFromNDArrayBoostConverter::construct(
    PyObject *object,
    boost::python::converter::rvalue_from_python_stage1_data *data) {
  namespace python = boost::python;
  // Object is a borrowed reference, so create a handle indicting it is
  // borrowed for proper reference counting.
  python::handle<> handle(python::borrowed(object));

  // Obtain a handle to the memory block that the converter has allocated
  // for the C++ type.
  typedef python::converter::rvalue_from_python_storage<Mat> storage_type;
  void *storage = reinterpret_cast<storage_type *>(data)->storage.bytes;

  // Allocate the C++ type into the converter's memory block, and assign
  // its handle to the converter's convertible variable.  The C++
  // container is populated by passing the begin and end iterators of
  // the python object to the container's constructor.
  PyArrayObject *oarr = (PyArrayObject *)object;

  bool needcopy = false, needcast = false;
  int typenum = PyArray_TYPE(oarr), new_typenum = typenum;
  int type = typenum == NPY_UBYTE    ? CV_8U
             : typenum == NPY_BYTE   ? CV_8S
             : typenum == NPY_USHORT ? CV_16U
             : typenum == NPY_SHORT  ? CV_16S
             : typenum == NPY_INT    ? CV_32S
             : typenum == NPY_INT32  ? CV_32S
             : typenum == NPY_FLOAT  ? CV_32F
             : typenum == NPY_DOUBLE ? CV_64F
                                     : -1;

  if (type < 0) {
    needcopy = needcast = true;
    new_typenum = NPY_INT;
    type = CV_32S;
  }

#ifndef CV_MAX_DIM
  const int CV_MAX_DIM = 32;
#endif
  int ndims = PyArray_NDIM(oarr);

  int size[CV_MAX_DIM + 1];
  size_t step[CV_MAX_DIM + 1];
  size_t elemsize = CV_ELEM_SIZE1(type);
  const npy_intp *_sizes = PyArray_DIMS(oarr);
  const npy_intp *_strides = PyArray_STRIDES(oarr);
  bool ismultichannel = ndims == 3 && _sizes[2] <= CV_CN_MAX;

  for (int i = ndims - 1; i >= 0 && !needcopy; i--) {
    // these checks handle cases of
    //  a) multi-dimensional (ndims > 2) arrays, as well as simpler 1- and
    //  2-dimensional cases b) transposed arrays, where _strides[] elements go
    //  in non-descending order c) flipped arrays, where some of _strides[]
    //  elements are negative
    if ((i == ndims - 1 && (size_t)_strides[i] != elemsize) ||
        (i < ndims - 1 && _strides[i] < _strides[i + 1]))
      needcopy = true;
  }

  if (ismultichannel && _strides[1] != (npy_intp)elemsize * _sizes[2])
    needcopy = true;

  if (needcopy) {

    if (needcast) {
      object = PyArray_Cast(oarr, new_typenum);
      oarr = (PyArrayObject *)object;
    } else {
      oarr = PyArray_GETCONTIGUOUS(oarr);
      object = (PyObject *)oarr;
    }

    _strides = PyArray_STRIDES(oarr);
  }

  for (int i = 0; i < ndims; i++) {
    size[i] = (int)_sizes[i];
    step[i] = (size_t)_strides[i];
  }

  // handle degenerate case
  if (ndims == 0) {
    size[ndims] = 1;
    step[ndims] = elemsize;
    ndims++;
  }

  if (ismultichannel) {
    ndims--;
    type |= CV_MAKETYPE(0, size[2]);
  }
  if (!needcopy) {
    Py_INCREF(object);
  }

  cv::Mat *m =
      new (storage) cv::Mat(ndims, size, type, PyArray_DATA(oarr), step);
  m->u = g_numpyAllocator.allocate(object, ndims, size, type, step);
  m->allocator = &g_numpyAllocator;
  m->addref();
  data->convertible = storage;
}

} // end namespace pbcvt
#endif

hello_worldmodule.cpp

#define PY_ARRAY_UNIQUE_SYMBOL helloWorld_ARRAY_API

#include "pyboostcvconverter/pyboostcvconverter.hpp"
#include <Python.h>
#include <opencv2/highgui/highgui.hpp>

using namespace cv;

void mirror(Mat img) { flip(img, img, 1); }

/* Python wrapper for the mirror() function. */
static PyObject *pyMirror(PyObject *self, PyObject *args) {
  PyObject *ndArray;

  if (!PyArg_ParseTuple(args, "O", &ndArray))
    return NULL;

  Mat mat = pbcvt::fromNDArrayToMat(ndArray);

  mirror(mat);

  return pbcvt::fromMatToNDArray(mat);
}

static PyMethodDef HelloWorldMethods[] = {
    {"mirror", pyMirror, METH_VARARGS, "Mirrors an OpenCV image."},
    {NULL, NULL, 0, NULL}};

static struct PyModuleDef helloWorldModule = {
    PyModuleDef_HEAD_INIT, "helloWorld", /* name of module */
    "",                                  /* module documentation, may be NULL */
    -1, /* size of per-interpreter state of the module, or -1 if the module
           keeps state in global variables. */
    HelloWorldMethods};

PyMODINIT_FUNC PyInit_helloWorld(void) {
  return PyModule_Create(&helloWorldModule);
}

And the setup.py file:

from setuptools import setup, Extension

setup(
    name='helloWorld',
    version='1.0',
    description='Python Package with Hello World C++ Extension',
    ext_modules=[
        Extension(
            'helloWorld',
            sources=['pyboostcvconverter/pyboost_cv3_converter.cpp', 'hello_worldmodule.cpp'],
            include_dirs=[ "/home/xavier/.local/lib/python3.8/site-packages/numpy/core/include/numpy", "/usr/include/opencv4"],
            libraries=['opencv_core', 'opencv_highgui', 'opencv_videoio' , 'boost_python', 'boost_system'],
            py_limited_api=True)
    ],
)

The build works fine but the import send me that error:

ImportError: /usr/local/lib/python3.8/dist-packages/helloWorld-1.0-py3.8-linux-x86_64.egg/helloWorld.abi3.so: undefined symbol: _ZN5boost6python9converter8registry9push_backEPFPvP7_objectEPFvS5_PNS1_30rvalue_from_python_stage1_dataEENS0_9type_infoEPFPK11_typeobjectvE
>>> 

I think it comes from the boost library that is not properly include

How did you install boost? Use an OS package or build it yourself?

Does boost have a .so library? If so did you link against it?

I am not near a linux system to check.

You can use thec++filt program to convert the output in the error into a C++ name.