Skip to content

Commit

Permalink
add Python bindings installable with setup.py
Browse files Browse the repository at this point in the history
  • Loading branch information
valgur committed Mar 20, 2020
1 parent fab3b3f commit 44ddae4
Show file tree
Hide file tree
Showing 7 changed files with 244 additions and 53 deletions.
102 changes: 102 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -65,3 +65,105 @@ fabric.properties
*.out
*.app


# Created by https://www.gitignore.io/api/python
# Edit at https://www.gitignore.io/?templates=python

### Python ###
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class

# C extensions
*.so

# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
pip-wheel-metadata/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST

# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec

# Installer logs
pip-log.txt
pip-delete-this-directory.txt

# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
.hypothesis/
.pytest_cache/

# Translations
*.mo
*.pot

# Scrapy stuff:
.scrapy

# Sphinx documentation
docs/_build/

# PyBuilder
target/

# pyenv
.python-version

# celery beat schedule file
celerybeat-schedule

# SageMath parsed files
*.sage.py

# Spyder project settings
.spyderproject
.spyproject

# Rope project settings
.ropeproject

# Mr Developer
.mr.developer.cfg
.project
.pydevproject

# mkdocs documentation
/site

# mypy
.mypy_cache/
.dmypy.json
dmypy.json

# Pyre type checker
.pyre/

12 changes: 7 additions & 5 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
cmake_minimum_required(VERSION 3.10)
cmake_minimum_required(VERSION 3.11)
project(surface_normal CXX)

find_package(OpenCV REQUIRED)
include_directories(${OpenCV_INCLUDE_DIRS} include)
link_libraries(${OpenCV_LIBS})

set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)

add_compile_options(-O3 -Wall -Wpedantic -Wno-sign-compare)

include_directories(${OpenCV_INCLUDE_DIRS})
link_libraries(${OpenCV_LIBS})

add_executable(${PROJECT_NAME} surface_normal.cpp surface_normal.h)
include(FetchContent)
FetchContent_Declare(pybind11 GIT_REPOSITORY https://github.com/pybind/pybind11)
FetchContent_MakeAvailable(pybind11)
pybind11_add_module(${PROJECT_NAME} src/python.cpp src/surface_normal.cpp)
23 changes: 23 additions & 0 deletions include/surface_normal.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#pragma once

#include <vector>

#include <opencv2/core.hpp>

using Plane = cv::Vec4f;

struct CameraIntrinsics {
float f;
float cx;
float cy;
};

cv::Mat3f normals_from_depth(const cv::Mat &depth, CameraIntrinsics intrinsics, int window_size,
float rel_dist_threshold);

cv::Mat3b normals_to_rgb(const cv::Mat3f &normals);

cv::Mat1f get_surrounding_points(const cv::Mat &depth, int i, int j, CameraIntrinsics intrinsics,
size_t window_size, float threshold);

cv::Vec3f fit_plane(const cv::Mat &points);
65 changes: 65 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import os
import re
import sys
import platform
import subprocess

from setuptools import setup, Extension
from setuptools.command.build_ext import build_ext
from distutils.version import LooseVersion


class CMakeExtension(Extension):
def __init__(self, name, sourcedir=''):
Extension.__init__(self, name, sources=[])
self.sourcedir = os.path.abspath(sourcedir)


class CMakeBuild(build_ext):
def run(self):
try:
out = subprocess.check_output(['cmake', '--version'])
except OSError:
raise RuntimeError("CMake must be installed to build the following extensions: " +
", ".join(e.name for e in self.extensions))

if platform.system() == "Windows":
cmake_version = LooseVersion(re.search(r'version\s*([\d.]+)', out.decode()).group(1))
if cmake_version < '3.1.0':
raise RuntimeError("CMake >= 3.1.0 is required on Windows")

for ext in self.extensions:
self.build_extension(ext)

def build_extension(self, ext):
extdir = os.path.abspath(os.path.dirname(self.get_ext_fullpath(ext.name)))
cmake_args = ['-DCMAKE_LIBRARY_OUTPUT_DIRECTORY=' + extdir,
'-DPYTHON_EXECUTABLE=' + sys.executable]

cfg = 'Debug' if self.debug else 'Release'
build_args = ['--config', cfg]

if platform.system() == "Windows":
cmake_args += ['-DCMAKE_LIBRARY_OUTPUT_DIRECTORY_{}={}'.format(cfg.upper(), extdir)]
if sys.maxsize > 2 ** 32:
cmake_args += ['-A', 'x64']
build_args += ['--', '/m']
else:
cmake_args += ['-DCMAKE_BUILD_TYPE=' + cfg]
build_args += ['--', '-j2']

env = os.environ.copy()
env['CXXFLAGS'] = '{} -DVERSION_INFO=\\"{}\\"'.format(env.get('CXXFLAGS', ''),
self.distribution.get_version())
if not os.path.exists(self.build_temp):
os.makedirs(self.build_temp)
subprocess.check_call(['cmake', ext.sourcedir] + cmake_args, cwd=self.build_temp, env=env)
subprocess.check_call(['cmake', '--build', '.'] + build_args, cwd=self.build_temp)


setup(
name='surface_normal',
ext_modules=[CMakeExtension('cmake_example')],
cmdclass=dict(build_ext=CMakeBuild),
zip_safe=False,
)
42 changes: 42 additions & 0 deletions src/python.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
#include <string>
#include <tuple>

#include <opencv2/core.hpp>
#include <opencv2/imgcodecs.hpp>
#include <opencv2/imgproc.hpp>

#include <pybind11/pybind11.h>

#include "surface_normal.h"

namespace py = pybind11;

using CameraIntrinsicsTuple = std::tuple<double, double, double>;

void normals_from_depth_wrapper(const std::string &in_img_path, const std::string &out_img_path,
CameraIntrinsicsTuple intrinsics_tuple, int window_size,
float rel_dist_threshold = 0.1) {
CameraIntrinsics intrinsics{};
intrinsics.f = std::get<0>(intrinsics_tuple);
intrinsics.cx = std::get<1>(intrinsics_tuple);
intrinsics.cy = std::get<2>(intrinsics_tuple);
cv::Mat depth = cv::imread(in_img_path, cv::IMREAD_ANYDEPTH | cv::IMREAD_GRAYSCALE);
depth.convertTo(depth, CV_32F);
cv::Mat3f normals = normals_from_depth(depth, intrinsics, window_size, rel_dist_threshold);
cv::Mat3b normals_rgb = normals_to_rgb(normals);
cvtColor(normals_rgb, normals_rgb, cv::COLOR_RGB2BGR);
imwrite(out_img_path, normals_rgb);
}

PYBIND11_MODULE(surface_normal, m) {
m.doc() = "";
m.def("normals_from_depth", &normals_from_depth_wrapper, py::arg("in_img_path"),
py::arg("out_img_path"), py::arg("intrinsics"), py::arg("window_size") = 15,
py::arg("rel_dist_threshold") = 0.1);

#ifdef VERSION_INFO
m.attr("__version__") = VERSION_INFO;
#else
m.attr("__version__") = "dev";
#endif
}
28 changes: 5 additions & 23 deletions surface_normal.cpp → src/surface_normal.cpp
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
#include <vector>

#include <opencv2/core.hpp>
#include <opencv2/imgcodecs.hpp>
#include <opencv2/imgproc.hpp>

#include "surface_normal.h"

Mat1f get_surrounding_points(const Mat &depth, int i, int j, CameraParams intrinsics,
using namespace cv;

Mat1f get_surrounding_points(const Mat &depth, int i, int j, CameraIntrinsics intrinsics,
size_t window_size, float threshold) {
float f_inv = 1.f / intrinsics.f;
float cx = intrinsics.cx;
Expand Down Expand Up @@ -58,7 +58,7 @@ Vec3f fit_plane(const Mat &points) {
return normal;
}

Mat3f normals_from_depth(const Mat &depth, CameraParams intrinsics, int window_size,
Mat3f normals_from_depth(const Mat &depth, CameraIntrinsics intrinsics, int window_size,
float rel_dist_threshold) {
Mat3f normals = Mat::zeros(depth.size(), CV_32FC3);
for (int i = 0; i < depth.rows; i++) {
Expand Down Expand Up @@ -87,7 +87,7 @@ Mat3f normals_from_depth(const Mat &depth, CameraParams intrinsics, int window_s

constexpr uint8_t f2b(float x) { return static_cast<uint8_t>(127.5 * (1 - x)); }

Mat3b normals_to_rgb(const Mat &normals) {
Mat3b normals_to_rgb(const Mat3f &normals) {
Mat3b res = Mat::zeros(normals.size(), CV_8UC3);
for (int i = 0; i < normals.rows; i++) {
for (int j = 0; j < normals.cols; j++) {
Expand All @@ -101,21 +101,3 @@ Mat3b normals_to_rgb(const Mat &normals) {
}
return res;
}

int main() {
CameraParams intrinsics{};
intrinsics.f = 721.5377;
intrinsics.cx = 596.5593;
intrinsics.cy = 149.854;
int window_size = 15;
float rel_dist_threshold = 0.1;

Mat depth = cv::imread("gt.png", cv::IMREAD_ANYDEPTH | cv::IMREAD_GRAYSCALE);
depth.convertTo(depth, CV_32F);
Mat3f normals = normals_from_depth(depth, intrinsics, window_size, rel_dist_threshold);
Mat3b normals_rgb = normals_to_rgb(normals);
cvtColor(normals_rgb, normals_rgb, COLOR_BGR2RGB);
cv::imwrite("gt_out.png", normals_rgb);

return 0;
}
25 changes: 0 additions & 25 deletions surface_normal.h

This file was deleted.

0 comments on commit 44ddae4

Please sign in to comment.