LoginSignup
2
1

More than 3 years have passed since last update.

cv::Vec型変数を変換するときに少ない記述量で書く方法 その2

Last updated at Posted at 2019-04-08

tl;dr

カスタムで高速な画像処理を実装したいときは、C++とOpenCV(cv::Mat)を使って実装したいはず。cv::Matの画素はVec型だけど、前回、この型の変換処理をラムダ式を使って簡潔に書ける小ネタ的な関数を紹介した。今回は、その関数とMat::forEachを使ってMatの画素値すべてを変換するやりかた。forEachは、並列化してくれるので、高速な画素値の変換コードを簡潔に書ける。ちなみに、最高速な変換コードを書きたいなら、それなりのやり方があるので、今回は簡潔に書くことに重点を置いています。ターゲットはC++17。Visual Studio 2017 15.9.11で動作確認。

本題

細かい説明よりも、まずコードから。conv関数の解説は、前回を参照のこと。

サンプルコード1
#include <iostream>
#include <opencv2/opencv.hpp>
#include <opencv2/highgui/highgui.hpp>

using namespace cv;
using namespace std;

template <class VECD, class FUNC, class VEC>
VECD conv(const VEC& s, FUNC func) 
{
    constexpr int ch = VEC::channels < VECD::channels ? VEC::channels : VECD::channels;
    VECD ret;
    for (int i = 0; i < ch; i++) {
        ret[i] = func(s[i]);
    }
    return ret;
}

template <class VEC, class FUNC>
VEC conv(const VEC& s, FUNC func)
{
    constexpr int ch = VEC::channels;
    VEC ret;
    for (int i = 0; i < ch; i++) {
        ret[i] = func(s[i]);
    }
    return ret;
}

//今回初登場
template <class VECD, class FUNC, class VEC>
void conv(const VEC& s, VECD& d,FUNC func)
{
    constexpr int ch = VEC::channels < VECD::channels ? VEC::channels : VECD::channels;
    for (int i = 0; i < ch; i++) {
        func(s[i], d[i]);
    }
}

//使い方
int main()
{
    Mat src = imread("c:\\temp\\picture.jpg");
    Mat dst(src.size(),CV_8UC3);

    src.forEach<Vec3b>([&dst](const auto& pix, const int* pos) {
        dst.at<Vec3b>(pos[0],pos[1]) = conv(pix, [](const auto& v) {
            return v / 2;
        });
    });

    imshow("dark image", dst);
    waitKey();

    return 0;
}

解説

解説するほどのものでもないが、Mat::forEach<T>のラムダ式の第一引数は、T型の画素値を渡す。サンプルコード1の例で言えば、TはVec3b。今回は、const auto&として、pix変数(Vec3b型)で受けた。pix変数(Vec3b型)をconv関数に渡してやると、conv関数のラムダ式の第一引数に、b,g,r値をそれぞれ渡しながら、ラムダ式が呼び出される。 例のように値を半分にしてreturnしてあげると、conv関数の戻り値はそれぞれの要素が半分になったVec3b型を返してくれる。これを参照でキャプチャしたdstの画素に代入してあげれば、全画素分処理できる。ちなみにサンプルコード2のように、Mat_<T>::forEachを使ってもよい。Mat3bは、Mat_<Vec3b>のtypedef。自分は、Vec3bを書くことが不要なサンプルコード2の書き方のほうが好き。
型変換を伴う場合、サンプルコード3と4の書き方があるが、どちらを選ぶかは、好みの問題かもしれない。

サンプルコード2
int main()
{
    Mat3b src = imread("c:\\temp\\picture.jpg");
    Mat3b dst(src.size());

    src.forEach([&dst](const auto& pix, const int* pos) {
        dst(pos[0],pos[1]) = conv(pix, [](const auto& v) {
            return v / 2;
        });
    });

    imshow("dark image", dst);
    waitKey();

    return 0;
}
サンプルコード3
int main()
{
    Mat3b src = imread("c:\\temp\\picture.jpg");
    Mat3f dst(src.size());

    src.forEach([&dst](const auto& pix, const int* pos) {
        dst(pos[0],pos[1]) = conv<Vec3f>(pix, [](const auto& v) {
            return v * 0.5f;
        });
    });

    return 0;
}
サンプルコード4
int main()
{
    Mat3b src = imread("c:\\temp\\picture.jpg");
    Mat3f dst(src.size());

    src.forEach([&dst](const auto& pix, const int* pos) {
        conv(pix, dst(pos[0], pos[1]), [](const auto& s, auto& d) {
            d =  s * 0.5f;
        });
    });

    return 0;
}

補足

なお、ソースは無保証でソースを使用したことによって発生した損害に対して、責任を負いません。

2
1
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
2
1