Skip to content
Maria Gotman edited this page Nov 9, 2015 · 8 revisions

Let's take for example cv::Tonemap class from photo module:

class CV_EXPORTS_W Tonemap : public Algorithm
{
public:
    CV_WRAP virtual void process(InputArray src, OutputArray dst) = 0;

    CV_WRAP virtual float getGamma() const = 0;
    CV_WRAP virtual void setGamma(float gamma) = 0;
};

CV_EXPORTS_W Ptr<Tonemap> createTonemap(float gamma = 1.0f);

This class inherits from cv::Algorithm, so let's create that first. We use torch.class:

./cv/Classes.lua

require 'cv'

local ffi = require 'ffi'

ffi.cdef[[
struct PtrWrapper {
    void *ptr;
};
]]

local C = ffi.load(libPath('Classes'))

-- ***** Algorithm *****

ffi.cdef[[
]]

do
    local Algorithm = torch.class('cv.Algorithm')
end

Our class will have the only field of type struct PtrWrapper, which wraps a pointer to our Algorithm, created in C++. The member functions will call corresponding C functions. I stick to the convention to name them <class-name>_<method-name>(); for example, Algorithm_getDefaultName(). If a function is a constructor or a destructor, <method-name> is ctor or dtor.

These are some required functions for Algorithm -- (look for them in opencv2/core.hpp : class Algorithm):

ffi.cdef[[
struct PtrWrapper Algorithm_ctor();

void Algorithm_dtor(struct PtrWrapper ptr);

const char *Algorithm_getDefaultName(struct PtrWrapper ptr);
]]

./include/Classes.hpp

#include <opencv2/core.hpp>
#include <Common.hpp>

// Algorithm

struct AlgorithmPtr {
    void *ptr;

    inline cv::Algorithm * operator->() { return static_cast<cv::Algorithm *>(ptr); }
    inline AlgorithmPtr(cv::Algorithm *ptr) { this->ptr = ptr; }
};

extern "C"
struct AlgorithmPtr Algorithm_ctor();

extern "C"
void Algorithm_dtor(AlgorithmPtr ptr);

extern "C"
const char *Algorithm_getDefaultName(AlgorithmPtr ptr);

Note that every such function (except constructor) takes PtrWrapper first. AlgorithmPtr is simply PtrWrapper that is "redefined" for a specific class for convenience (note the operators for implicit conversion). This is a "hack": while in FFI cdefs we declare functions taking PtrWrapper, in C we operate <class-name>Ptr. There should be no errors as these structs are identical.

Here are the implementations of a ctor, a dtor and one member function (the remaining are similar to implement):

./src/Classes.cpp

#include <Classes.hpp>

// Algorithm

extern "C"
struct AlgorithmPtr Algorithm_ctor() {
    // The pointer returned by new is implicitly converted
    return new cv::Algorithm();
}

extern "C"
void Algorithm_dtor(AlgorithmPtr ptr) {
    delete static_cast<cv::Algorithm *>(ptr.ptr);
}

extern "C"
const char *Algorithm_getDefaultName(AlgorithmPtr ptr) {
    // Thanks to operator-> redefiniton
    return ptr->getDefaultName().c_str();
}

Now let's insert the Lua templates. Again -- the code should be self-explanatory.

./cv/Classes.lua

do
    local Algorithm = torch.class('cv.Algorithm')

    function Algorithm:__init()
        self.ptr = ffi.gc(C.Algorithm_ctor(), C.Algorithm_dtor)
    end

    function Algorithm:getDefaultName()
    	return ffi.string(C.Algorithm_getDefaultName(self.ptr))
    end
end

Let's return to cv::Tonemap. Now it should be obvious how to bind this class, basing on Algorithm. Don't forget to pass self.ptr!

struct TonemapPtr {
    void *ptr;

    inline cv::Tonemap * operator->() { return static_cast<cv::Tonemap *>(ptr); }
    inline TonemapPtr(cv::Tonemap *ptr) { this->ptr = ptr; }
};

extern "C" struct TonemapPtr Tonemap_ctor();

extern "C" struct TensorWrapper Tonemap_process(struct TonemapPtr ptr, struct TensorArray src, struct TensorWrapper dst);

extern "C" float Tonemap_getGamma(struct TonemapPtr ptr);

extern "C" void Tonemap_setGamma(struct TonemapPtr ptr, float gamma);

But you may notice that the only way to create Tonemap is calling createTonemap(), which returns a shared pointer (cv::Ptr<cv::Tonemap>). That's why this will result in early destruction of our just created object:

extern "C"
struct TonemapPtr Tonemap_ctor()
{
    // FAILS!
    // Tonemap will be destroyed right after return 
    return cv::createTonemap();
}

Even worse, just as boost::shared_ptr, cv::Ptr doesn't allow to manually detach itself. That's why we exploit yet another hack to access cv::Ptr's private fields:

./include/Classes.hpp

// This is simply cv::Ptr with all fields made public
template <typename T>
struct PublicPtr
{
public:
    cv::detail::PtrOwner* owner;
    T* stored;
};

// increfs a cv::Ptr and returns the pointer
template <typename T>
T *rescueObjectFromPtr(cv::Ptr<T> ptr) {
    PublicPtr<T> *publicPtr = reinterpret_cast<PublicPtr<T> *>(&ptr);
    publicPtr->owner->incRef();
    return ptr.get();
}

Having this, problem is solved by

./src/photo.cpp

extern "C" struct TonemapPtr Tonemap_ctor()
{
    return rescueObjectFromPtr(cv::createTonemap());
}

Other (minor) differences are:

  • declare inheritance explicitly in torch.class()
  • use Algorithm_dtor() for destruction (as it's virtual)
  • Algorithm_dtor() comes from another lib (namespace), so refer to it by Classes.Algorithm_dtor

./cv/photo.lua

...

--- ***************** Classes *****************
require 'cv.Classes'

local Classes = ffi.load(cv.libPath('Classes'))

ffi.cdef[[
struct PtrWrapper Tonemap_ctor();

struct TensorWrapper Tonemap_process(struct PtrWrapper ptr, struct TensorArray src, struct TensorWrapper dst);

float Tonemap_getGamma(struct PtrWrapper ptr);

void Tonemap_setGamma(struct PtrWrapper ptr, float gamma);
]]

-- Tonemap

do
    local Tonemap = torch.class('cv.Tonemap', 'cv.Algorithm')

    function Tonemap:__init()
        self.ptr = ffi.gc(C.Tonemap_ctor(), Classes.Algorithm_dtor)
    end

    function Tonemap:process(t)
        local argRules = {
            {"src", required = true},
            {"dst", default = nil}
        }
        local src, dst = cv.argcheck(t, argRules)
    
        C.Tonemap_process(self.ptr, cv.wrap_tensor(src), cv.wrap_tensor(dst));
    end

    function Tonemap:getGamma()
        return C.Tonemap_getGamma(self.ptr);
    end
    
    function Tonemap:setGamma(t)
        local argRules = {
            {"gamma", required = true}
        }
        local gamma = cv.argcheck(t, argRules)

        C.Tonemap_setGamma(self.ptr, gamma)
    end 
end

Done! Check out ./demo/LineSegmentDetector.lua for class usage example.

Clone this wiki locally