■ ベタ書き実装:何だ…? この行列の計算が終わるまであと5秒はかかるはずだ。なぜお前はもう動ける……?
サブスレッド:お前の前にいるのは、重たい計算をバックグラウンドで実行するようにメインスレッドから指示され、平行で非同期実行してきたサブスレッドだ。
「メインスレッド、cv::AsyncArray.get()しろ」
■はじめに:平行処理使ってますか?
画像処理、それも複雑な機械学習を使っていると、どうしても処理が非常に重くなっちゃうってことありますよね?
確かにOpenCVはマルチコア対応がかなり進んではいます。
だけど、重たい処理は分けたいなあ・・・ プラットフォームへの依存性は最小限にしつつ、それでも平行処理は夢みたいなあ、って気持ちありませんかね?
どーんと、お任せください。OpenCVには、こうした画像処理の並行処理も書きやすくなっています。
■cv::AyncPromiseから始める非同期処理入門
ではまず、サンプルコードから行きましょうかね?
// g++ main.cpp -o a.out -I/usr/local/include/opencv4 -lopencv_core -lopencv_imgcodecs -lpthread
#include <iostream>
#include <thread>
#include <chrono>
#include <opencv2/core.hpp>
#include <opencv2/imgcodecs.hpp> // 結果出力用
#include <opencv2/core/detail/async_promise.hpp> // 環境により必要
void heavyComputation(cv::AsyncPromise& promise) {
const std::string tag = "[Worker] ";
std::cout << tag << "計算開始(5秒かかります)..." << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(5));
cv::Mat mat(1080, 1920, CV_8UC3, cv::Scalar(0, 0, 0));
for(int y = 0; y < mat.rows; y++) {
mat.row(y).setTo(cv::Scalar(y % 255, 128, 255 - (y % 255)));
}
promise.setValue(mat);
std::cout << tag << "値をセットしました。" << std::endl;
}
int main() {
const std::string tag = "[Main] ";
cv::AsyncPromise promise;
cv::AsyncArray future_mat = promise.getArrayResult();
// promiseの参照を渡す!
std::thread t(heavyComputation, std::ref(promise));
std::cout << tag << "別の作業をしています..." << std::endl;
for (int i = 0; i < 3; ++i) {
std::this_thread::sleep_for(std::chrono::seconds(1));
std::cout << tag << "経過... " << i + 1 << "s" << std::endl;
}
cv::Mat result;
std::cout << tag << "結果を待機(get)します..." << std::endl;
future_mat.get(result);
if (!result.empty()) {
cv::imwrite("output.png", result);
std::cout << tag << "完了! output.png を確認してください。" << std::endl;
}
t.join(); // tのクロージング
return 0;
}
▢ main側解説
cv::AsyncPromise promise;
cv::AsyncArray future_mat = promise.getArrayResult();
cv::AyncPromise のインスタンス promiseから、getArrayResult()を介してcv::AyncArrayのインスタンスを参照します。なお、このgetArrayResult()は一度しか読んじゃダメらしいので要注意!
// promiseの参照を渡す!
std::thread t(heavyComputation, std::ref(promise));
C++のstd::threadに、後述するheavyComputation関数とその引数としてstd::ref(promise)を渡し、スレッドtを生成します。なお、ここでstd::ref()で参照として渡す必要があります。
cv::Mat result;
std::cout << tag << "結果を待機(get)します..." << std::endl;
future_mat.get(result);
future_mat.get()が呼ばれると、先ほどのheavyComputation関数側で、setValue()するまで待機します。完了すると、resultにデータが格納されている、はずです。
t.join(); // tのクロージング
お作法として、クロージングしておきましょう。
▢ heavyComputeation()
void heavyComputation(cv::AsyncPromise& promise) {
promiseは参照型であることがポイントです!
promise.setValue(mat);
計算結果のcv::Matを、promise.setValue()にセットしてあげましょう。これによってmain thread側からこの値を参照できるようになります!
ということで、OpenCVを使うのであれば、並列処理も簡単に実装できますね!
■まとめ
- OpenCVには、平行処理のためのクラスもあります!
- cv::AcyncPromiseの受け渡し方法は要注意。何なら参照使えばよいと覚えてもOK
- メインスレッドAyncArray.get()すると、サブスレッドでsetValueするまで待つ!やったね!
以上です、ありがとうございました!