はじめに
C++版のSDKは公開されているけれどPython版がない、という時のために、具体例をもとにPythonバインディングを書く最低限の手順をまとめてみました。今回は、DJI社から公開されているThermal SDKをモジュール化してみます。
pybind11
pybind11は、C++とPythonをバインドするためのライブラリです。詳しい使い方はこちらをご参照ください。
手順
pybind11のインストール
必要に応じて、venv等で仮想環境を構築して実施してください。
$ pip install pybind11
パッケージフォルダの構成
予め前述のサイトからdji_thermal_sdk_v1.0_20201110.zipをダウンロードしておきます。
$ mkdir -p Thermal-SDK-Python/src
$ cd Thermal-SDK-Python
$ unzip dji_thermal_sdk_v1.0_20201110.zip
$ mv dji_thermal_sdk_v1.0_20201110/ sdk
バインディングの実装
今回は、rjpegという名前のモジュールを作成します。PYBIND11_MODULEマクロを使用してクラスや関数を定義していきます。
#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
namespace py = pybind11;
PYBIND11_MODULE(rjpeg, m) {
m.doc() = "Thermal SDK for Python";
...
}
enum の定義
pybind11::enum_<>を使用して定義します。以下に一例を書きます。
PYBIND11_MODULE(rjpeg, m) {
...
py::enum_<dirp_verbose_level_e>(m, "dirp_verbose_level_e")
.value("DIRP_VERBOSE_LEVEL_NONE", DIRP_VERBOSE_LEVEL_NONE)
.value("DIRP_VERBOSE_LEVEL_DEBUG", DIRP_VERBOSE_LEVEL_DEBUG)
.value("DIRP_VERBOSE_LEVEL_DETAIL", DIRP_VERBOSE_LEVEL_DETAIL)
.value("DIRP_VERBOSE_LEVEL_NUM", DIRP_VERBOSE_LEVEL_NUM)
.export_values();
}
struct/classの定義
pybind11::class_<>を使用して定義します。以下に一例を書きます。メンバ変数の配列はそのままでは登録できないので、stl::vectorを使用してstruct/classを定義し直しています。詳細は公式ドキュメントをご参照ください。
PYBIND11_MODULE(rjpeg, m) {
...
py::class_<dirp_resolotion_t>(m, "dirp_resolotion_t")
.def_readwrite("width", &dirp_resolotion_t::width)
.def_readwrite("height", &dirp_resolotion_t::height);
}
関数の定義
def()関数に関数名、関数エントリポイント、説明を渡して定義します。引数や戻り値によっては元の関数をそのまま使えないor Pythonとして適切でない場合があるので、その場合はWrapper関数を作成します。インスタンスを戻り値とする場合、C++のメモリ管理を考慮する必要があります。詳細は、Return value policiesをご参照ください。
PYBIND11_MODULE(rjpeg, m) {
...
m.def("dirp_create_from_rjpeg", &__dirp_create_from_rjpeg, "Create a new DIRP handle with specified R-JPEG binary data");
}
setup.pyの作成
インストール用のスクリプトを作成します。SDKのlibdirp.soをリンクしています。
from setuptools import setup
from setuptools import find_packages
from pybind11.setup_helpers import Pybind11Extension, build_ext
from pybind11 import get_cmake_dir
import inspect
import os
import platform
import shutil
import sys
__version__ = "0.0.1"
def get_dirp_dir():
system = platform.system().lower()
arch = 'x64' if platform.architecture()[0] == '64bit' else 'x86'
return 'sdk/tsdk-core/lib/' + system + '/release_' + arch
ext_modules = [
Pybind11Extension(
"rjpeg",
["src/rjpeg_module.cpp"],
package_dir='rjpeg',
define_macros = [('VERSION_INFO', '"' + __version__ + '"')],
include_dirs=['sdk/tsdk-core/api'],
library_dirs=[get_dirp_dir()],
libraries=['dirp']
),
]
setup(
name="rjpeg",
version=__version__,
author="Satoshi Tsutsumi",
author_email="satoshi.tsutsumi@gmail.com",
url="https://github.com/SatoshiTsutsumi/Thermal-SDK-Python",
description="Thermal SDK for Python",
long_description="",
ext_modules=ext_modules,
install_requires=['pybind11>=2.7'],
extras_require={"test": "pytest"},
cmdclass={"build_ext": build_ext},
zip_safe=False
)
実行例
以下に、RJPEG形式の画像を読み込んで表示する例を示します。APIをそのままラップしたので画像を表示するのが手間です。もう少し使いやすいAPIにした方が良いかもしれません。
以下はThermal SDKに含まれるdataset/H20T/DJI_0001_R.JPGを表示した結果です。
import numpy as np
from PIL import Image
from rjpeg import *
# Register app
dirp_register_app("DJI_TSDK")
with open("sdk/dataset/H20T/DJI_0001_R.JPG ", "rb") as f:
# Get DIRP handle
buf = f.read()
h = dirp_create_from_rjpeg(buf, len(buf))
# Get resolution
resol = dirp_get_rjpeg_resolution(h)
# Get colored image
color_img_buf = dirp_process(h)
color_img_array = np.array(color_img_buf, dtype=np.uint8)
color_img_array = color_img_array.reshape([resol.height, resol.width, 3])
color_img = Image.fromarray(color_img_array)
color_img.show()
参照
上記のソースコードを公開しています。pybind11は初心者なので、ご指摘歓迎です。