At work, I had to do image processing using Python. If it is within the range that can be done with OpenCV etc., that is fine, but the filter of the original algorithm does not have a practical speed unless it is C / C ++. So, I made a module for Python in C language.
If you want to try it out quickly, please use my repository. https://github.com/soramimi/pymodule-image-filter
The first trial is hello world
, so it is unnecessary, but since numpy, pillow, and matplotlib are used in the later image processing section, these modules need to be installed. It is supposed to be developed on Ubuntu. You need a Python development package.
sudo apt install python3-dev python3-matplotlib
.c
static PyMethodDef myMethods[] = {
{ "helloworld", helloworld, METH_NOARGS, "My helloworld function." },
{ NULL }
};
The contents of the definition are described in the order of {(function name), (pointer to function), (argument passing method), (description)}.
METH_NOARGS
is for functions with no arguments. Use METH_VARARGS
to use the argument.
.c
static struct PyModuleDef mymodule = {
PyModuleDef_HEAD_INIT,
"mymodule",
"Python3 C API Module",
-1,
myMethods
};
Write the module name and description.
.c
PyMODINIT_FUNC PyInit_mymodule(void)
{
import_array();
return PyModule_Create(&mymodule);
}
Write the function call ʻimport_array () as needed. This is necessary because this theme deals with arrays in image processing. Not needed if you don't use arrays. You need to run ʻimport_array ()
in all source code that uses the array API.
.c
static PyObject *helloworld(PyObject *self, PyObject *args)
{
fprintf(stderr, "Hello, world\n");
return Py_None;
}
It's the first trial function, so it's hello world
. Later, we will implement the code that performs image processing.
setup.py
from distutils.core import setup, Extension
setup(name = 'mymodule', version = '1.0.0', ext_modules = [Extension('mymodule', ['mymodule.c'])])
python3 setup.py build_ext -i
If you have a .so
file with a long name, you are successful.
$ ls *.so
mymodule.cpython-35m-x86_64-linux-gnu.so
main.py
import mymodule
mymodule.helloworld()
Run the Python program and module .so
files in the same directory.
$ python3 main.py
Hello, world
As an example, let's make a function that applies a sepia-toned filter. The function name has been changed from hello world
to sepia
.
The program on the Python side is as follows.
main.py
from PIL import Image
import numpy as np
import matplotlib.pyplot as plt
import mymodule
im = np.array(Image.open('kamo.jpg'))
im = mymodule.sepia(im)
print(type(im))
print(im.dtype)
print(im.shape)
plt.imshow(im)
plt.show()
It works as follows.
The function definition is as follows.
.c
{ "sepia", sepia, METH_VARARGS, "Sepia tone image filter" },
This is an excerpt from the C language source.
mymodule.c
static PyObject *sepia(PyObject *self, PyObject *args)
{
PyArrayObject *srcarray;
if (!PyArg_ParseTuple(args, "O", &srcarray)) {
fprintf(stderr, "invalid argument\n");
return Py_None;
}
The first (but only one) argument is an array. Get it as an object.
The input array is a three-dimensional array of (height) x (width) x (channel), otherwise an error will occur. The number of channels is limited to 3 (RGB).
.c
if (srcarray->nd != 3) {
fprintf(stderr, "invalid image\n");
return Py_None;
}
if (srcarray->dimensions[2] != 3) {
fprintf(stderr, "invalid image\n");
return Py_None;
}
Gets the size of the image.
.c
int h = srcarray->dimensions[0];
int w = srcarray->dimensions[1];
Allocate an array to store the filtered image.
.c
npy_intp dims[] = { h, w, 3 };
PyObject *image = PyArray_SimpleNew(3, dims, NPY_UBYTE);
if (!image) {
fprintf(stderr, "failed to allocate array\n");
return Py_None;
}
Do not mistake the order of (height) x (width) x (channel).
The object secured as an array can be cast as a pointer to the array structure as it is.
.c
PyArrayObject *dstarray = (PyArrayObject *)image;
Apply a filter.
.c
for (int y = 0; y < h; y++) {
uint8_t const *src = (uint8_t const *)srcarray->data + y * w * 3;
uint8_t *dst = (uint8_t *)dstarray->data + y * w * 3;
for (int x = 0; x < w; x++) {
uint8_t r = src[x * 3 + 0];
uint8_t g = src[x * 3 + 1];
uint8_t b = src[x * 3 + 2];
r = pow(r / 255.0, 0.62) * 205 + 19;
g = pow(g / 255.0, 1.00) * 182 + 17;
b = pow(b / 255.0, 1.16) * 156 + 21;
dst[x * 3 + 0] = r;
dst[x * 3 + 1] = g;
dst[x * 3 + 2] = b;
}
}
Return the object and finish.
.c
return image;
}
that's all.
Error handling is omitted. In case the function returns None
(Py_None
), such as when an invalid image file is received, a practical program should handle the error appropriately.
As I wrote at the beginning, the full source code is ** here ) **.
Recommended Posts