画像処理・画像認識のプログラムを書くと、次の画像の先読みをしたくなる。今回は、Python言語からC++言語での書き換えを勉強してみる。
以下の手順をふまえた。
- 逐次処理版を書く。
- その言語でのマルチスレッドの枠組みを確認する。
- どうマルチスレッド化が可能か考える。
- マルチスレッド版を実装する
逐次処理版を書く。
逐次処理版
#include <opencv2/opencv.hpp>
#include <vector>
#include <fstream>
int main (int argc, const char * argv[])
{
if (argc == 1){
std::cout << "usage " << argv[0] << " imageListName" << std::endl;
exit(0);
}
std::cout << argv[1] << std::endl;
cv::namedWindow("pedestrian", cv::WINDOW_AUTOSIZE);
cv::Mat img;
cv::Mat imgNew;
cv::Mat imgOld;
cv::HOGDescriptor hog;
hog.setSVMDetector(cv::HOGDescriptor::getDefaultPeopleDetector());
std::ifstream ifs("imgList.txt");
std::string str;
std::string strNew;
int64 start = cv::getTickCount();
ifs >> str;
img = cv::imread(str, 1);
while (!ifs.eof())
{
std::vector<cv::Rect> found, found_filtered;
hog.detectMultiScale(img, found, 0, cv::Size(8,8), cv::Size(32,32), 1.05, 2);
size_t i, j;
for (i=0; i<found.size(); i++)
{
cv::Rect r = found[i];
for (j=0; j<found.size(); j++)
if (j!=i && (r & found[j]) == r)
break;
if (j== found.size())
found_filtered.push_back(r);
}
for (i=0; i<found_filtered.size(); i++)
{
cv::Rect r = found_filtered[i];
r.x += cvRound(r.width*0.1);
r.width = cvRound(r.width*0.8);
r.y += cvRound(r.height*0.07);
r.height = cvRound(r.height*0.8);
cv::rectangle(img, r.tl(), r.br(), cv::Scalar(0,255,0), 3);
}
cv::imshow("pedestrian", img);
if (cv::waitKey(10)>=0)
break;
ifs >> strNew;
imgNew = cv::imread(strNew, 1);
if (imgNew.empty()){
break;
}
imgOld = img;
img = imgNew;
}
int64 end = cv::getTickCount();
double elapsedSec = (end - start) / cv::getTickFrequency();
std::cout << elapsedSec << "s" << std::endl;
return 0;
}
###その言語でのマルチスレッドの枠組みを確認する。
C++11の規格では、OSや特定のライブラリに依存しないマルチスレッドの枠組みが用意された。Windowsでプロトタイピングして、Linux(ARM)に移植するのにも使えそうだ。Visual Studio Community 2013でもC+11はサポートしている。
今回の処理では、人物の検出処理を行っている間に、次の画像を読んでおけばいいだけなので、非同期のスレッドについての記述に目を向けた。
std::asyncの使い方
#include <future>
#include <thread>
(略)
std::future<関数の戻り値の型> result = std::async(std::launch::async, 関数, 関数への引数);
//ここに他の処理
//次の一文で、スレッド処理された関数の戻り値を取得できる。
スレッド処理された関数の戻り値 = result.get();
どうマルチスレッド化が可能か考える。
ファイルの読み込みの部分は、CPU以外の部分がボトルネックになっていると推定されるので、別スレッドにすることによる処理時間の短縮が少しはあるときたして、書き換えることにする。
std::asyncの使い方(動作したコードの一部)
cv::Mat imgNew;
std::string strNew;
(略)
std::future<cv::Mat> result = std::async(std::launch::async, myImread, strNew);
//ここに検出処理
imgNew = result.get();
ここで、関数の引数を1つだけとした版の関数をmyImreadとして定義している。
ラムダ式を用いた関数の定義をしなくてもすむ。
cv::Mat myImread(std::string name){
return cv::imread(name, 1);
}
非同期のマルチスレッド版
#include <opencv2/opencv.hpp>
#include <vector>
#include <fstream>
#include <future>
#include <thread>
cv::Mat myImread(std::string name){
return cv::imread(name, 1);
}
int main (int argc, const char * argv[])
{
if (argc == 1){
std::cout << "usage " << argv[0] << " imageListName" << std::endl;
exit(0);
}
std::cout << argv[1] << std::endl;
cv::namedWindow("pedestrian", cv::WINDOW_AUTOSIZE);
cv::Mat img;
cv::Mat imgNew;
cv::Mat imgOld;
cv::HOGDescriptor hog;
hog.setSVMDetector(cv::HOGDescriptor::getDefaultPeopleDetector());
std::ifstream ifs("imgList.txt");
std::string str;
std::string strNew;
int64 start = cv::getTickCount();
ifs >> str;
img = cv::imread(str, 1);
while (true)
{
ifs >> strNew;
cv::Mat imgNew;
std::future<cv::Mat> result = std::async(std::launch::async, myImread, strNew);
std::vector<cv::Rect> found, found_filtered;
hog.detectMultiScale(img, found, 0, cv::Size(8,8), cv::Size(32,32), 1.05, 2);
size_t i, j;
for (i=0; i<found.size(); i++)
{
cv::Rect r = found[i];
for (j=0; j<found.size(); j++)
if (j!=i && (r & found[j]) == r)
break;
if (j== found.size())
found_filtered.push_back(r);
}
for (i=0; i<found_filtered.size(); i++)
{
cv::Rect r = found_filtered[i];
r.x += cvRound(r.width*0.1);
r.width = cvRound(r.width*0.8);
r.y += cvRound(r.height*0.07);
r.height = cvRound(r.height*0.8);
cv::rectangle(img, r.tl(), r.br(), cv::Scalar(0,255,0), 3);
}
imgNew = result.get();
if (imgNew.empty()){
break;
}
cv::imshow("pedestrian", img);
if (cv::waitKey(10)>=0)
break;
imgOld = img;
img = imgNew;
}
int64 end = cv::getTickCount();
double elapsedSec = (end - start) / cv::getTickFrequency();
std::cout << elapsedSec << "s" << std::endl;
return 0;
}
現時点では、とりあえず動作しているが、目的の処理時間の短縮にどれだけ寄与したかは解析していない。
参考したURL
1:C++で簡単非同期処理(std::thread,std::async)
http://qiita.com/termoshtt/items/d3cb7fe226cdd498d2ef
2:C++入門 std::async
http://kaworu.jpn.org/cpp/std::async