boost python / boost numpy を使った numpy array と C++配列の相互変換


## はじめに
江坂キャンパスから梅田キャンパスで動画編集ソフト作ってた人です
自作動画編集ソフト制作においてC++配列とnumpy配列を変換した時に集めた情報をまとめておきます

前の記事 [WebAssemblyを使ってJavaScriptからC++を動かしてみよう！](https://qiita.com/Shio_3001/items/56938511a0cdfccd8e80)


## boost pythonとは何か
端的にいうと「C++とpythonを連携できるようにするもの」です
[boost pythonについてはこちらの記事が参考になると思います](https://qiita.com/m4saka/items/1e2c24e80212a4c4c584)
numpy配列とC++配列の変換についてまとめられている記事が少なかったので今回書きました

boost pythonの使い方についてはドキュメントなどを読む方がいいと思います(boost pythonの使い方記事はそのうち書きます)

## 前提
:::note warn
C++側の配列は1次元にしましょう
C++の配列をわざわざ1 < n次元で扱うなんてめんどくさいです
:::

参考程度に
2次元配列を1次元として扱う式
指定先 = width * y + x
3次元配列を1次元として扱う式(r=3次元目)
指定先 = (width * y + x) * Rsize + r
この計算式が使えるのは各次元方向で要素数が全て等しい場合に限ります=画像データ取り扱い時に重宝します

## 本題

### C++からNumpy
こっちは簡単 突っ込むだけ

```C++:cpp_to_py.cpp
#include <bits/stdc++.h>
#include <math.h>
#include <stdio.h>
#include <boost/python.hpp>
#include <boost/python/numpy.hpp>
#include <iomanip>
namespace py = boost::python;
namespace np = boost::python::numpy;

np::ndarray CppPy()
{
    int draw_RGB = 1280 * 720;
    int draw_RGBA = 1280 * 720 * 4; //1280x720でRGBA
    uint8_t *draw = new uint8_t[draw_RGBA];

    for (int i = 0; i < draw_RGB; i++)
    {
        draw[i * 4 + 0] = 0; //RGBA
        draw[i * 4 + 1] = 255;
        draw[i * 4 + 2] = 255;
        draw[i * 4 + 3] = 255;
    }

    py::tuple shape = py::make_tuple(draw_RGBA);
    py::tuple stride = py::make_tuple(sizeof(uint8_t));
    np::dtype dt = np::dtype::get_builtin<uint8_t>();
    np::ndarray npdraw = np::from_data(&draw[0], dt, shape, stride, py::object());
    //np::from_dataの第一引数に目的の配列の先頭のアドレスを入れる

    return npdraw;
}

BOOST_PYTHON_MODULE(cpp_to_py)
{
    Py_Initialize();
    np::initialize();
    def("CppPy", &CppPy);
}

```

```python:test.py
import cpp_to_py
import cv2

draw = cv2.cvtColor(cpp_to_py.CppPy().astype('uint8').reshape(720, 1280, -1), cv2.COLOR_RGBA2BGR)
‪#C++側からもらったnumpyデータ(1次元)をopencvで表示できるようuint8に変換して3次元変換

#ここから先はopencvのGUIに表示するコード
print(draw)
print(draw.shape)
print(draw.dtype)
cv2.imshow("draw", draw)
cv2.waitKey(0)
cv2.destroyAllWindows()
```
一面水色の画像が表示される

### NumpyからC++
ループとポインタが必要 めんどくさい

```C++:py_to_cpp.cpp
#include <bits/stdc++.h>
#include <math.h>
#include <stdio.h>

#include <iomanip>

#include <boost/python.hpp>
#include <boost/python/numpy.hpp>
namespace py = boost::python;
namespace np = boost::python::numpy;

np::ndarray PyCpp(np::ndarray npdraw, int width, int hight)
{
    auto pointer_start = reinterpret_cast<uint8_t *>(npdraw.get_data());
    auto strides = npdraw.get_strides();

    int rgba = 4;

    int draw_RGBA = width * hight * rgba; //1280x720でRGBA
    int draw_RGB = width * hight;

    uint8_t *draw = new uint8_t[draw_RGBA];

    for (int i = 0; i < draw_RGBA; i++)
    {
        uint8_t *p_color = pointer_start + i;
        draw[i] = *p_color;
    }

    //ここまでcppへの変換
    //さっきのと組み合わせてpythonに戻してみる

    py::tuple shape = py::make_tuple(draw_RGBA);
    py::tuple stride = py::make_tuple(sizeof(uint8_t));
    np::dtype dt = np::dtype::get_builtin<uint8_t>();
    np::ndarray npdraw_for_py = np::from_data(&draw[0], dt, shape, stride, py::object());
    //np::from_dataの第一引数に目的の配列の先頭のアドレスを入れる

    return npdraw_for_py;
}

BOOST_PYTHON_MODULE(py_to_cpp)
{
    Py_Initialize();
    np::initialize();
    def("PyCpp", &PyCpp);
}
```

```python:testnp.py
import py_to_cpp
import numpy as np
import cv2

X = 1280
Y = 720

draw_inp = np.full((Y, X, 4), 255)
draw_inp[:, :, 0] = np.full((Y, X), 255)
draw_inp[:, :, 1] = np.full((Y, X), 255)
draw_inp[:, :, 2] = np.full((Y, X), 0)
draw_inp[:, :, 3] = np.full((Y, X), 255)
# 無理やり黄色を作る

draw = cv2.cvtColor(py_to_cpp.PyCpp(draw_inp.astype('uint8'), X, Y).astype('uint8').reshape(Y, X, -1), cv2.COLOR_RGBA2BGR)

print(draw)
print(draw.shape)
print(draw.dtype)
cv2.imshow("draw", draw)
cv2.waitKey(0)
cv2.destroyAllWindows()

```
一面黄色の画像が表示される

:::note alert
ポインタの型の定義に注意です
:::
送られてくるnumpy配列のデータ型に合わせる必要があります。(例:uint8のnumpyをC++に変換するならC++側のポインタの型はuint8_tを使う)

uint_8のnumpy配列なのにint型のポインタを使用するなど、ポインタのbitサイズがあっていない場合はセグメンテーション違反:11が発生します。またuint_8と同じサイズのchar型を使うと違反11を回避することはできますが、意図しない数値が出てきます

## 使用上の注意
基本的にはnumpyのスライスなどを活用してpython側で処理を済ましてしまいましょう
決して「pythonでループを流すな軍団」への反対材料として利用してはいけません

それでは良いC++Pythonライフを