-
Notifications
You must be signed in to change notification settings - Fork 48
Wrapping a single function
Say we're implementing imgproc
module bindings for Torch. So let's see what we have in it by inspecting opencv2/imgproc.hpp
: it contains function signatures and their doc descriptions.
It looks like there's cv::GaussianBlur
function:
CV_EXPORTS_W void GaussianBlur(InputArray src, OutputArray dst, Size ksize,
double sigmaX, double sigmaY = 0,
int borderType = BORDER_DEFAULT);
Note the CV_EXPORTS
macro -- from it, we infer the need to export this function into foreign language bindings. As can be seen from the signature and the docs, this filter is intended for application in 3 ways:
(1) output to an empty cv::Mat:
cv::Mat image = imread('img.png');
cv::Mat blurred;
cv::GaussianBlur(image, blurred, cv::Size(3, 3), 1.0, 1.0);
(2) output to a cv::Mat of the same size and type as src:
cv::Mat image = imread('img.png');
cv::Mat blurred = image.clone() * 0;
cv::GaussianBlur(image, blurred, cv::Size(3, 3), 1.0, 1.0);
(3) filter in-place:
cv::Mat image = imread('img.png');
cv::GaussianBlur(image, image, cv::Size(3, 3), 1.0, 1.0);
Let's make these use cases possible for our Torch users!
We start off by defining a Lua function in ./cv/imgproc.lua
:
function cv.GaussianBlur(t)
-- function body
end
Note that this function belongs to cv
table.
We want cv.GaussianBlur
to accept a table of arguments t
, so the calls for those use cases should look like this:
-- output to retval, as dst is not provided!
local image_B = cv.GaussianBlur{src=image, ksize={7, 7}, sigmaX=3.5, sigmaY=3.5}
-- output to another Tensor of same size & type
local image_A = image * 0
cv.GaussianBlur{src=image, dst=image_A, ksize={7, 7}, sigmaX=3.5, sigmaY=3.5}
-- or filter in-place
cv.GaussianBlur{src=image, dst=image, ksize={width=7, height=7}, sigmaX=3.5, sigmaY=3.5}
So let's convert t
's fields to local variables representing arguments, with regard to the original function signature:
- If
var
argument is required, we writeassert(t.var)
to ensure it's present - If
var
is optional and has a default value, we writet.var or <default-value>
./cv/imgproc.lua
function cv.GaussianBlur(t)
local src = assert(t.src)
local dst = t.dst
local ksize = assert(t.ksize) -- how to convert this?
local sigmaX = assert(t.sigmaX)
local sigmaY = t.sigmaY or 0
local borderType = t.borderType or cv.BORDER_DEFAULT
-- to be continued
end
Some things to note here:
-
src
anddst
are assumed to betorch.Tensor
s -
dst
is optional because of the first use case - Almost all (more than 1000) OpenCV constants and enums like
cv.BORDER_DEFAULT
are defined in./cv/constants.lua
And another thing: how to cope with that argument of type cv::Size
? A great deal of OpenCV functions require argument(s) of some OpenCV class type, like cv::Size
, cv::Point
, cv::TermCriteria
, cv::RotatedRect
etc. We have to somehow create their instances in Lua, that's why for each such class cv::<class-name>
we create a C wrapper struct <class-name>Wrapper
in common code (cv.lua
and Common.[hpp|cpp]
). So let's wrap our cv::Size
:
./include/Common.hpp
struct SizeWrapper {
int width, height;
// an operator for implicit automatic conversion to cv::Size
inline operator cv::Size() { return cv::Size(width, height); }
};
./cv.lua
function cv.Size(data)
return ffi.new('struct SizeWrapper', data)
end
Here, we exploit the intelligence of this ffi.new
function: if you want to make a struct SizeWrapper
from cv::Size(150, 280)
, you may pass either of the following to cv.Size
:
150, 280
{150, 280}
{width=150, height=280}
Cool. Now we can directly convert t.ksize
to struct SizeWrapper
:
./cv/imgproc.lua
function cv.GaussianBlur(t)
local src = assert(t.src)
local dst = t.dst
local ksize = cv.Size(assert(t.ksize))
local sigmaX = assert(t.sigmaX)
local sigmaY = t.sigmaY or 0
local borderType = t.borderType or cv.BORDER_DEFAULT
-- a call to a C function that invokes cv::GaussianBlur goes here
end
Now let's turn to our C function that will be called through FFI. According to how we've prepared our args, the signature has to be as follows:
./include/imgproc.hpp
extern "C" struct TensorWrapper GaussianBlur(
struct TensorWrapper src, struct TensorWrapper dst,
struct SizeWrapper ksize, double sigmaX,
double sigmaY, int borderType
);
Remember the 3 use cases of this filter? That's what we have to implement in ./src/imgproc.cpp
. The code should be self-explanatory:
./src/imgproc.cpp
extern "C"
struct TensorWrapper GaussianBlur(struct TensorWrapper src, struct TensorWrapper dst,
struct SizeWrapper ksize, double sigmaX,
double sigmaY, int borderType)
{
// first, check if dst is provided
if (dst.isNull()) {
// [use case 1]: output to return value
cv::Mat retval;
cv::GaussianBlur(
src.toMat(), retval, ksize, sigmaX, sigmaY, borderType);
// create a NEW Tensor in C and pass it to Lua
return TensorWrapper(retval);
} else if (dst.tensorPtr == src.tensorPtr) {
// [use case 3]: filter in-place
cv::Mat source = src.toMat();
cv::GaussianBlur(
source, source, ksize, sigmaX, sigmaY, borderType);
} else {
// [use case 2]: try to output to a Tensor dst of same size & type as src
cv::GaussianBlur(
src.toMat(), dst.toMat(), ksize, sigmaX, sigmaY, borderType);
}
return dst;
}
Note that:
-
struct TensorWrapper
can't be converted tocv::Mat
implicitly. Instead, you have to call.toMat()
. -
ksize
is passed tocv::GaussianBlur
directly asSizeWrapper::operator cv::Size()
is defined. It would be also correct to writecv::Size(ksize)
. - There's also a
TensorWrapper::operator cv::Mat
, so areturn retval
could be fine, but I constructTensorWrapper
explicitly for code readability. - Some OpenCV functions provide a
cv::noArray()
default value forInputArray
/OutputArray
arguments. Unfortunately, this can't be constructed in Lua, so you should useTO_MAR_OR_NOARRAY(tensor)
macro.
Next, compile the libs:
mkdir build
cd build
cmake ..
# or, optionally: `cmake .. -DOpenCV_DIR=<path-to-your-opencv-3.x.x>`
Our function now resides in ./lib/libimgproc.so
(for Linux and the like) so we can call it through FFI:
./cv/imgproc.lua
function cv.GaussianBlur(t)
local src = assert(t.src)
local dst = t.dst
local ksize = cv.Size(assert(t.ksize))
local sigmaX = assert(t.sigmaX)
local sigmaY = t.sigmaY or 0
local borderType = t.borderType or cv.BORDER_DEFAULT
return cv.unwrap_tensors(
C.GaussianBlur(
cv.wrap_tensors(src), cv.wrap_tensors(dst), ksize, sigmaX, sigmaY, borderType))
end
cv.wrap_tensors(tensors)
is a special function that takes one or more Tensor
s (either in a table or not) and returns a TensorWrapper
(or a TensorArray
) over them. It should be applied to every torch.Tensor
that is going to be passed to C++ via FFI.
cv.unwrap_tensors(wrapper)
converts a TensorWrapper
or a TensorArray
to a torch.Tensor
(or to a list of them).
That's it! See also ./demo/filtering.lua
.