LoginSignup
0
0

ncnnとOpenCVとの間で画像データを変換する

Last updated at Posted at 2023-12-19

概要

ncnnでは、画像データをncnn::Matという形式で保持します。
OpenCVのcv::Matとで、画像データを変換してやり取りする方法を紹介します。

ncnn::Mat

ncnnでは、画像データをncnn::Matという形式で保持します。
ncnn::Mat の主要な変数は以下のようになっています。

class NCNN_EXPORT Mat{
public:
    int w;
    int h;
    int d;
    int c;
    int dims;

    void* data;
    int* refcount;
    size_t elemsize;
    int elempack;
    Allocator* allocator;
    size_t cstep;
};
Member Description
h 画像の高さ、整数型で設定。
w 画像の幅、整数型で設定。
c チャンネル数、整数型で設定
d 画像の深さ,通常は1。 (3次元畳み込み処理で使用)
dim 画像の次元数。通常はh/w/cの3次元。
data データが格納されている先頭アドレス
refcount 参照カウント
elemsize/elempack Mat 内の各要素のサイズ.
allocator Mat オブジェクトのメモリ割り当て方法
cstep Mat のデータレイアウトは CHW 順であり,この値は各 2 チャンネル間のストライドを表します.

elemsize/elempackについて

Mat内の各要素は型付けされておらず,要素のサイズに関する情報をelemsize/elempacで持たせています.
1つの長いサイズの型の値を、複数の短いサイズの型の値として格納した場合には、elemsize/elempackで表現します。elempackは、simdレジスタとよく対応しています。異なる型の値を格納するために1つの非常に広いレジスタを使用します。

TH elemsize elempack
double 8 1
float 4 1
int 4 1
short 2 1
signed char 1 1
arm neon elemsize elempack
float64x2_t 16 2
float32x4_t 16 4
int32x4_t 16 4
float16x4_t 8 4
int8x8_t char 8 8

elempack が 2 の場合、値の実数は 2 倍ですが、Mat 構造体のビューでは、幅の広い値は 1 つの値として扱われます。例えば,40 個の浮動小数点値を Mat オブジェクトに格納したい場合, elempack 1 が使用されれば Mat の幅は 40 となり, elempack 4 が使用されれば 10 となります.

dims w h c cstep elemsize elempack
1 40 1 1 40 4 1
1 10 1 1 10 16 4

ncnn::Matへの各要素へのアクセスの仕方

以下に、ncnn::Matへの各要素をコンソールに出力するプログラムを記載します。
float型のポインタを使って、各要素へアクセスをすることができます。

void pretty_print(const ncnn::Mat& m){
    for (int q=0; q<m.c; q++){
        const float* ptr = m.channel(q);
        for (int z=0; z<m.d; z++){
            for (int y=0; y<m.h; y++){
                for (int x=0; x<m.w; x++){
                    std::cout<<ptr[x]<<" "<<std::endl;
                }
                ptr += m.w;
                std::cout<<std::endl;
            }
            std::cout<<std::endl;
        }
        std::cout<<"("------------------------"<<std::endl;
    }
}

OpenCVのcv::Matからncnn::Matへの変換

OpenCVのcv::Matからncnn::Matへの変換は、ncnn::Mat::from_pixels()関数を使うことで変換を行うことができます。

cv::Mat CV_8UC3 → ncnn::Mat 3 channel + BGR→RGBへのchannel変換 への変換

// cv::Mat a(h, w, CV_8UC3);
ncnn::Mat in = ncnn::Mat::from_pixels(a.data, ncnn::Mat::PIXEL_BGR2RGB, a.cols, a.rows);

cv::Mat CV_8UC3 → ncnn::Mat 3 channel + channel変換をしない への変換

// cv::Mat a(h, w, CV_8UC3);
ncnn::Mat in = ncnn::Mat::from_pixels(a.data, ncnn::Mat::PIXEL_RGB, a.cols, a.rows);

cv::Mat CV_8UC3 → ncnn::Mat 1 channel + RGB→Grayへのchannel変換 への変換

// cv::Mat rgb(h, w, CV_8UC3);
ncnn::Mat inrgb = ncnn::Mat::from_pixels(rgb.data, ncnn::Mat::PIXEL_RGB2GRAY, rgb.cols, rgb.rows);

cv::Mat CV_8UC3 → ncnn::Mat 1 channel + BGR→Grayへのchannel変換 への変換

// cv::Mat bgr(h, w, CV_8UC3);
ncnn::Mat inbgr = ncnn::Mat::from_pixels(bgr.data, ncnn::Mat::PIXEL_BGR2GRAY, bgr.cols, bgr.rows);

cv::Mat CV_8UC1 → ncnn::Mat 1 channel への変換

// cv::Mat a(h, w, CV_8UC1);
ncnn::Mat in = ncnn::Mat::from_pixels(a.data, ncnn::Mat::PIXEL_GRAY, a.cols, a.rows);

cv::Mat CV_32FC1 → ncnn::Mat 1 channel

データのコピーを避けるために、ncnn::Matを構築し、そこに直接データを入力することができます。

// cv::Mat a(h, w, CV_32FC1);
ncnn::Mat in(a.cols, a.rows, 1, (void*)a.data);
in = in.clone();

cv::Mat CV_32FC3 → ncnn::Mat 3 channel への変換

CV_32FC3型を変換する場合には、ncnn::Matを構築し、そこに直接データを入力することができます。

// cv::Mat a(h, w, CV_32FC3);
ncnn::Mat in_pack3(a.cols, a.rows, 1, (void*)a.data, (size_t)4u * 3, 3);
ncnn::Mat in;
ncnn::convert_packing(in_pack3, in, 1);

std::vector < cv::Mat > + CV_32FC1 → ncnn::Mat multiple channels への変換

CV_32FC3型を変換する場合には、ncnn::Matを構築し、そこに直接データを入力することができます。

// std::vector<cv::Mat> a(channels, cv::Mat(h, w, CV_32FC1));
int channels = a.size();
ncnn::Mat in(a[0].cols, a[0].rows, channels);
for (int p=0; p<in.c; p++)
{
    memcpy(in.channel(p), (const uchar*)a[p].data, in.w * in.h * sizeof(float));
}

ncnn::MatからOpenCVのcv::Matへの変換

ncnn::MatからOpenCVのcv::Matへの変換は、to_pixels()関数を使うことで変換を行うことができます。
float側でのデータをやり取りする場合には、memcpy()関数でのデータコピーを行います。

ncnn::Mat 3 channel → cv::Mat CV_8UC3 + BGR→RGBへのchannel変換

行列inが値を0〜1の範囲でとる場合には、最初にin.substract_mean_normalize()を呼び出して、値を0〜1から0〜255にスケールする必要があります。

// ncnn::Mat in(w, h, 3);
cv::Mat a(in.h, in.w, CV_8UC3);
in.to_pixels(a.data, ncnn::Mat::PIXEL_BGR2RGB);

ncnn::Mat 3 channel → cv::Mat CV_8UC3 + channel変換しない

行列inが値を0〜1の範囲でとる場合には、最初にin.substract_mean_normalize()を呼び出して、値を0〜1から0〜255にスケールする必要があります。

// ncnn::Mat in(w, h, 3);
cv::Mat a(in.h, in.w, CV_8UC3);
in.to_pixels(a.data, ncnn::Mat::PIXEL_RGB);

ncnn::Mat 1 channel → cv::Mat CV_8UC1 への変換

行列inが値を0〜1の範囲でとる場合には、最初にin.substract_mean_normalize()を呼び出して、値を0〜1から0〜255にスケールする必要があります。

// ncnn::Mat in(w, h, 1);
cv::Mat a(in.h, in.w, CV_8UC1);
in.to_pixels(a.data, ncnn::Mat::PIXEL_GRAY);

ncnn::Mat 1 channel → cv::Mat CV_32FC1への変換

memcpyでのデータコピーを行います。

// ncnn::Mat in;
cv::Mat a(in.h, in.w, CV_32FC1);
memcpy((uchar*)a.data, in.data, in.w * in.h * sizeof(float));

ncnn::Mat 3 channel → cv::Mat CV_32FC3への変換

ncnn::Mat データを直接操作して、memcpyでのデータコピーを行います。

// ncnn::Mat in(w, h, 3);
ncnn::Mat in_pack3;
ncnn::convert_packing(in, in_pack3, 3);
cv::Mat a(in.h, in.w, CV_32FC3);
memcpy((uchar*)a.data, in_pack3.data, in.w * in.h * 3 * sizeof(float));

ncnn::Mat multiple channels → std::vector < cv::Mat > + CV_32FC1への変換

memcpyでのデータコピーを行います

// ncnn::Mat in(w, h, channels);
std::vector<cv::Mat> a(in.c);
for (int p=0; p<in.c; p++)
{
    a[p] = cv::Mat(in.h, in.w, CV_32FC1);
    memcpy((uchar*)a[p].data, in.channel(p), in.w * in.h * sizeof(float));
}

参考資料

この記事を作成するにあたり、以下のウェブサイトを参考にしました。

ncnn Wiki/use ncnn with opencv

NCNN Mat详解

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0