Skip to content

Commit 86abca7

Browse files
committed
initial commit
0 parents  commit 86abca7

File tree

5 files changed

+215
-0
lines changed

5 files changed

+215
-0
lines changed

CMakeLists.txt

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
cmake_minimum_required(VERSION 3.10)
2+
3+
project(python_cpp_module)
4+
5+
find_package(OpenCV 4 REQUIRED COMPONENTS core imgproc highgui)
6+
find_package(PythonInterp 3.6 REQUIRED)
7+
find_package(PythonLibs "${PYTHON_VERSION_STRING}" EXACT REQUIRED)
8+
execute_process(
9+
COMMAND "${PYTHON_EXECUTABLE}" -c "import numpy; print(numpy.get_include())"
10+
OUTPUT_VARIABLE NUMPY_INCLUDE_DIR
11+
OUTPUT_STRIP_TRAILING_WHITESPACE
12+
RESULT_VARIABLE NUMPY_NOT_FOUND
13+
)
14+
if(NUMPY_NOT_FOUND)
15+
message(FATAL_ERROR "NumPy headers not found")
16+
endif()
17+
18+
set(CMAKE_CXX_STANDARD 11)
19+
set(CMAKE_CXX_STANDARD_REQUIRED ON)
20+
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
21+
22+
set(target_name python_cpp_module)
23+
add_library(${target_name} MODULE cpp_module.cpp)
24+
25+
target_include_directories(${target_name} PRIVATE src/ ${PYTHON_INCLUDE_DIRS} ${NUMPY_INCLUDE_DIR})
26+
target_link_libraries(${target_name} ${PYTHON_LIBRARIES} opencv_core opencv_imgproc opencv_highgui)
27+
set_target_properties(${target_name} PROPERTIES PREFIX "")
28+
if(WIN32)
29+
set_target_properties(${target_name} PROPERTIES SUFFIX ".pyd")
30+
endif()

README.md

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# Sample code of Python extension module
2+
3+
### Description:
4+
This project includes C++ Python extension module which handles Numpy objects. Numpy object and OpenCV Mat object interaction code example is also included.
5+
6+
![image](./resources/result.jpg)
7+
8+
### How to build:
9+
```sh
10+
build.bat
11+
```
12+
13+
### Note:
14+
Tested on Win10 with MSVC 2019.

cpp_module.cpp

+144
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
#define PY_SSIZE_T_CLEAN
2+
#include <Python.h>
3+
4+
#include <iostream>
5+
#include <stdexcept>
6+
#include <vector>
7+
8+
#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION
9+
#include "numpy/arrayobject.h"
10+
11+
#include <opencv2/opencv.hpp>
12+
13+
extern "C";
14+
15+
// Simple addition test function (return = in1 + in2)
16+
static PyObject* test_func(PyObject* self, PyObject* args) {
17+
int input1, input2;
18+
if (!PyArg_ParseTuple(args, "ii", &input1, &input2)) { // Arguments are 2 integer values ("ii")
19+
return nullptr;
20+
}
21+
PyObject *return_value = PyLong_FromLong(input1 + input2); // Create object to return
22+
return return_value;
23+
}
24+
25+
// Simple image processing function (Inverse image color)
26+
static PyObject* test_image_processing(PyObject* self, PyObject* args) {
27+
PyArrayObject* input_image;
28+
PyObject* output_image;
29+
if(!PyArg_ParseTuple(args, "O!", &PyArray_Type, &input_image)) { // Argument is an Numpy array object
30+
return nullptr;
31+
}
32+
output_image = PyArray_NewLikeArray(input_image, NPY_ANYORDER, NULL, 0); // Create object to return
33+
34+
int ndim = PyArray_NDIM(input_image); // Number of dimensions of the input Numpy array
35+
npy_intp *shape;
36+
shape = PyArray_SHAPE(input_image); // Shape of the input Numpy array (npy_intp[])
37+
38+
// Obtain data buffer pointers
39+
char* in_buf = static_cast<char*>(PyArray_DATA(input_image)); // PyArray_DATA() will return void*
40+
char* out_buf = static_cast<char*>(PyArray_DATA(reinterpret_cast<PyArrayObject*>(output_image)));
41+
42+
// Image processing
43+
size_t C = shape[2];
44+
size_t W = shape[1];
45+
size_t H = shape[0];
46+
for(size_t y=0; y<H; y++) {
47+
for(size_t x=0; x<W; x++){
48+
for(size_t c=0; c<C; c++) {
49+
out_buf[y*W*C + x*C + c] = 255 - in_buf[y*W*C + x*C + c];
50+
}
51+
}
52+
}
53+
54+
return output_image; // Returned object must exist after exiting of this C++ function
55+
}
56+
57+
// Simple image processing function using OpenCV (Canny edge detection)
58+
static PyObject* test_image_processing_OCV(PyObject* self, PyObject* args) {
59+
PyArrayObject* input_image;
60+
int th1, th2; // Threshold value for Canny edge detection
61+
if(!PyArg_ParseTuple(args, "O!ii", &PyArray_Type, &input_image, &th1, &th2)) { // 3 arguments (Numpy obj, int, int)
62+
return nullptr;
63+
}
64+
65+
shape = PyArray_SHAPE(input_image);
66+
67+
char* in_buf = static_cast<char*>(PyArray_DATA(input_image)); // PyArray_DATA() will return void*
68+
69+
// Image processing with OpenCV
70+
cv::Mat in_mat(shape[0] /*Width(rows)*/, shape[1] /*Height(cols)*/, CV_8UC3, in_buf); // Input cv::Mat generated from input Numpy object
71+
cv::Mat out_mat; // Mat object to store final image
72+
cv::Mat channels[3]; // Mat object for channel extraction
73+
cv::cvtColor(in_mat, in_mat, cv::COLOR_BGR2GRAY); // Color -> Gray scale
74+
cv::split(in_mat, channels); // Channel extraction. packed BGR -> [B, G, R]
75+
cv::Canny(channels[0], out_mat, th1, th2); // Canny edge detection
76+
77+
npy_intp out_shape[] = { out_mat.rows, out_mat.cols, out_mat.channels() };
78+
PyObject* result = PyArray_SimpleNew(3, out_shape, NPY_UINT8); // Create a Numpy object to return
79+
char* out_buf = static_cast<char*>(PyArray_DATA(reinterpret_cast<PyArrayObject*>(result))); // Obtain data buffer pointer of the final image
80+
memcpy(out_buf, out_mat.data, out_shape[0] * out_shape[1] * out_shape[2]); // Copy data buffer
81+
82+
return result; // Return created Numpy object
83+
}
84+
85+
// Numpy array attribute extraction test function
86+
static PyObject* npy_array_test(PyObject* self, PyObject* args) {
87+
PyArrayObject* input_image;
88+
89+
if(!PyArg_ParseTuple(args, "O!", &PyArray_Type, &input_image)) { // Argument is a Numpy object
90+
return nullptr;
91+
}
92+
93+
size_t ndims = PyArray_NDIM(input_image); // Number of dimensions
94+
std::cout << "#Dims: " << PyArray_NDIM(input_image) << std::endl;
95+
96+
npy_intp* shape = PyArray_SHAPE(input_image); // Shape
97+
for(size_t i=0; i<ndims; i++) {
98+
std::cout << "Dim" << i << ": " << shape[i] << std::endl;
99+
}
100+
std::cout << "NpyType: " << PyArray_TYPE(input_image) << std::endl; // Numpy data type
101+
std::cout << "ItemSize: " << PyArray_ITEMSIZE(input_image) << std::endl; // Size of item (element)
102+
std::cout << "TotalSize: " << PyArray_SIZE(input_image) << std::endl; // Total size (in size of item)
103+
std::cout << "TotalBytes: " << PyArray_NBYTES(input_image) << std::endl; // Total bytes
104+
uint8_t* buffer = static_cast<uint8_t*>(PyArray_DATA(input_image)); // PyArray_DATA() will return void*
105+
106+
// Show first 10 items of the Numpy array
107+
int bytes = PyArray_NBYTES(input_image);
108+
int disp_size = bytes<10 ? bytes : 10;
109+
for(size_t i=0; i<disp_size; i++) {
110+
std::cout << (int)buffer[i] << " ";
111+
}
112+
std::cout << std::endl;
113+
114+
return PyLong_FromLong(0);
115+
}
116+
117+
// Function definition table to export to Python
118+
PyMethodDef method_table[] = {
119+
{"test_func", static_cast<PyCFunction>(test_func), METH_VARARGS, "test method function of test module"},
120+
{"test_image_processing", static_cast<PyCFunction>(test_image_processing), METH_VARARGS, "test image processing function"},
121+
{"test_image_processing_OCV", static_cast<PyCFunction>(test_image_processing_OCV), METH_VARARGS, "test image processing function"},
122+
{"npy_array_test", static_cast<PyCFunction>(npy_array_test), METH_VARARGS, "test numpy array handling test"},
123+
{NULL, NULL, 0, NULL}
124+
};
125+
126+
// Module definition table
127+
PyModuleDef test_module = {
128+
PyModuleDef_HEAD_INIT,
129+
"python_cpp_module",
130+
"test module",
131+
-1,
132+
method_table
133+
};
134+
135+
// Initialize and register module function
136+
// Function name must be 'PyInit_'+module name
137+
// This function must be the only *non-static* function in the source code
138+
PyMODINIT_FUNC PyInit_python_cpp_module(void) {
139+
import_array(); // Required to receive Numpy object as arguments
140+
if (PyErr_Occurred()) {
141+
return nullptr;
142+
}
143+
return PyModule_Create(&test_module);
144+
}

resources/car_1.bmp

1.37 MB
Binary file not shown.

test.py

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# test.py
2+
3+
from python_cpp_module import test_func, test_image_processing, npy_array_test, test_image_processing_OCV
4+
import numpy as np
5+
import cv2
6+
7+
image_file = './resources/car_1.bmp'
8+
9+
# Simple addition function test
10+
print(test_func(1,3)) # Anser should be 4.
11+
12+
# Numpy array parameter extraction test
13+
a=np.zeros((480,640,3), dtype=np.uint8)
14+
a[0,0,0:3] = [5,6,7]
15+
npy_array_test(a)
16+
17+
# Simple image processing test (inverse color)
18+
img = cv2.imread(image_file)
19+
cv2.imshow('input', img)
20+
img=test_image_processing(img)
21+
cv2.imshow('inversed', img)
22+
23+
# Simple image processing test with OpenCV (Canny edge detection)
24+
img = cv2.imread(image_file)
25+
img = test_image_processing_OCV(img, 100, 200)
26+
cv2.imshow('Canny', img)
27+
cv2.waitKey(0)

0 commit comments

Comments
 (0)