前提・実現したいこと
C++でOpenCVを使い、円検出をしたいと思っています。
ネットを駆使し、円検出を行うことに成功したのですが、動画の再生速度が非常に遅くなってしまっています。(動画上の1秒が20秒ぐらい)
この問題を解決できる方いらっしゃいませんか。
よろしくお願いします。
環境
OpenCV ver.4.5.2
Visual studio 2019
windows10
該当のソースコード
C++
1# include <iostream> 2# include <opencv2/opencv.hpp> 3# include <opencv2/videoio.hpp> 4# include <opencv2/core/core.hpp> 5# include <opencv2/imgproc/imgproc.hpp> 6# include <opencv2/highgui/highgui.hpp> 7 8using namespace cv; 9using namespace std; 10 11#define WINDOW_NAME "Video" 12 13int main() { 14 cv::Mat img, frame, gray, gau; 15 cv::namedWindow(WINDOW_NAME, cv::WINDOW_NORMAL); 16 cv::VideoCapture cap("C:\Users\tomoya\VTS_09_1.mp41116_16.mp4"); //このように、動画ファイルのパスを指定する 17 //cv::moveWindow(WINDOW_NAME, 0, 0); 18 //cv::setWindowProperty(WINDOW_NAME, cv::WND_PROP_FULLSCREEN, cv::WINDOW_FULLSCREEN); //フルスクリーンにする 19 20 int v_w = cap.get(cv::CAP_PROP_FRAME_WIDTH); //縦の大きさ 21 int v_h = cap.get(cv::CAP_PROP_FRAME_HEIGHT); //横の大きさ 22 int max_frame = cap.get(cv::CAP_PROP_FRAME_COUNT); //フレーム数 23 int fps = cap.get(cv::CAP_PROP_FPS); //フレームレート 24 cout << "fps=", fps; 25 //cap.set(CAP_PROP_POS_FRAMES, 100); //100フレーム目から再生 26 27 for (int CFN = 0; CFN < max_frame; CFN++) { 28 cap >> frame; //1フレーム分取り出してframeに保持させる 29 img = frame; 30 cv::cvtColor(img, gray, COLOR_BGR2GRAY); 31 32 // Hough変換のための前処理(画像の平滑化を行なわないと誤検出が発生しやすい) 33 cv::GaussianBlur(gray, gau, cv::Size(11, 11), 2, 2); 34 35 // Hough変換による円の検出と検出した円の描画 36 std::vector<cv::Vec3f> circles; 37 cv::HoughCircles(gau, circles, HOUGH_GRADIENT, 1, 100, 20, 50); 38 39 std::vector<cv::Vec3f>::iterator i = circles.begin(); 40// std::vector<cv::Vec3f> circles; 41 for (; i != circles.end(); ++i) { 42 cv::Point center(cv::saturate_cast<int>((*i)[0]), cv::saturate_cast<int>((*i)[1])); 43 int radius = cv::saturate_cast<int>((*i)[2]); 44 cv::circle(frame, center, radius, cv::Scalar(255, 0, 0), 2); 45 cv::circle(frame, center, 3, cv::Scalar(255, 0, 0), -1); 46// p = (float*)cv::GetSeqElem(circles, i); 47// cv::circle(img, cv::Point(cvRound(p[0]), cvRound(p[1])), 3, CV_RGB(0, 255, 0), -1, 8, 0); 48// cv::circle(img, cv::Point(cvRound(p[0]), cvRound(p[1])), cvRound(p[2]), CV_RGB(255, 0, 0), 3, 8, 0); 49// Point center(cv::Round(circles[i][0]), cv::Round(circles[i][1])); 50 } 51 cv::imshow("Video", frame); 52 //waitKey(1); 53 int key = cv::waitKey(1); // 表示のために最低1ms待つ 54 if (key == 27) { break; }// esc or enterキーで終了 55 } 56}
また、pythonではうまくいったので、ご参考程度にpythonでの画像解析を載せておきます。
python
1import cv2 2import numpy as np 3from matplotlib import pyplot as plt 4import openpyxl 5 6#動画の読み込み 7filepath="動画のパス" 8cap=cv2.VideoCapture(filepath) 9while(cap.isOpened()): 10 a=cap.read() 11 if a[0] == False: 12 break 13 #フレームを取得 14 ret,frame=a 15 #グレースケール変換 16 gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) 17 gray = cv2.GaussianBlur(gray, (33,33), 1) 18 cimg=gray 19 20 #円検出 21 circles=cv2.HoughCircles(cimg,cv2.HOUGH_GRADIENT,1,20,param1=150,param2=10,minRadius=55,maxRadius=75) 22 #circles=cv2.HoughCircles(cimg,cv2.HOUGH_GRADIENT,1,15,param1=150,param2=17,minRadius=30,maxRadius=50) 23 if circles is not None and len(circles)>0: 24 circles = np.uint16(np.around(circles)) 25 for i in circles[0,:]: 26 #外側の円を描く(img,center,radius,BGR,thickness) 27 cv2.circle(frame,(i[0],i[1]),i[2],(255,0,0),2) 28 #円の中心を描く 29 cv2.circle(frame,(i[0],i[1]),1,(255,0,0),-1) 30 31 #フレームを表示 32 cv2.imshow("Frame",frame) 33 34 #qキーが押されたら途中終了 35 if cv2.waitKey(1) & 0xFF==ord('q'): 36 break
時間がかかってるのがOpenCVの処理のどれかの場合で、解像度(画素数)落としても円検出できるなら、
cv::cvtColor()
の次でimgをリサイズして画素数減らすと、改善するかも
ただし、それ以降の処理も、それに合わせた変更要ります
cv::circle()
の、円の中心座標と半径は、リサイズ前のものになるように調整します
cv::GaussianBlur()
や
cv::HoughCircles()
のパラメータは、リサイズに合わせて調整した方がいいかも
動画の全フレームで円検出する必要がないなら、必要なフレームだけ処理するとか
(4フレームの内の1フレームだけ検出とか)
参考 (Pythonの例ですが)
https://qiita.com/n_ueh/items/8b458a3c24f27afe81a8
> 表示のために最低1ms待つ
のであれば,
cv::waitKey() の引数は 1 とするんじゃないですかね.
(まぁ,ここを変えたところで速度面が改善するわけではないでしょうけども)
Pythonで以前作成した際には同じ動画を使用してきちんと再生されていたのですが、C++を使った際だけ画素数を落とさなければいけないということはあるのでしょうか?できれば精度よく円検出したいのですが、、
環境がかかれてないのですが、
Debugモードでやってるとかはないでしょうか?
すみません。環境を書いていなかったのですが、Debugなしで行っています。
fanaさんが指摘してるところを直してみたら、どうですか?
> Pythonで以前作成した際には同じ動画を使用してきちんと再生されていた
PythonのOpenCVは、共有ライブラリに処理を丸投げしてるので、違いはないはずですが、特定のバージョンでのバグかもしれないので、比較する際はOpenCVのバージョンを同じにした方がいいですよ
Python の実際のコードと比較して差異を見ない限りは比較はできないでしょう.
両者では関数に渡すパラメータが異なっているかもしれないし.
---
あと,これは速度に関して影響がどの程度出るかわからないけども
cvtColor や GaussianBlur で,入力と出力の両者を img として使っているのが気になる.
これだと毎回バッファの生成と破棄が起きてると思う:
・cvtColor では毎回出力画像バッファを新規に割り当てる必要があり,それはループ末尾で毎回破棄されるハズ
・GaussianBlurの実装がどうなってるかわからんけども,入力画像バッファの上だけでやれる処理とは到底思えないので,INとOUTに同一のMatを指定された場合には内部で相応の対応を行うんじゃないかな?
ループに入るよりも前の時点で必要なサイズはわかるのだから,「ループの外で出力用のバッファを用意しておき,ループ内ではそれを使いまわす」という形にできる.
大した労力でもないでしょうから,試してみてもよいのでは.
これだけ言われても環境書かないので何とも言えませんが、gccとかなら、うろ覚えですが-O2とかではSIMDとか使わなかったと思います。速度が重要なモジュールはカリカリにチューニングされると聞いたことがありますし、automatic vectorizationとかでググってみたほうがいいかも。
https://gcc.gnu.org/projects/tree-ssa/vectorization.html
①各所にタイマーをしこんで、どこが重いのか確認してみてはいかがでしょうか?
また、Pythonのソースもはると比較しやすいと思います。
②fpsはいくつですか?
③「OpenCV(おそらくver.4.5.2)」におそらくとありますが、
追加したlibファイル名をコピペではってもらえますでしょうか?
(コピペであることがこっそり重要)
④ img = frame;
cv::cvtColor(img, img, COLOR_BGR2GRAY);
↓
cv::cvtColor(frame, img, COLOR_BGR2GRAY);
でいいんじゃないかな
①タイマーというのがあるんですね。
少し調べてやってみようと思います。
②初学者で恐れ多いのですが、fpsをcout<<で出力しようとしたのですが、動画が表示されてfpsがうまく表示されません。(もしくは動画が最後まで行かないからfpsが表示されないのか)
③opencv_world452d
やはり4.5.2みたいです。
④そこは他の方の意見を参考に直させていただきました。
与えるパラメータがC++とPythonで違ってるせいですね。。。
ハフ変換で顕著なようです
> ③opencv_world452d
それはデバッグ用なので、
opencv_world452
を使ってみてください
Visual Studioのソリューション構成は、DebugじゃなくてReleaseですよね?
> ②fpsはいくつですか?
とうのは,
cap.get(cv::CAP_PROP_FPS);
で得られる数値のことではないと思いますよ.(情報として無価値ですし)
「秒間で何ループくらい処理できているのか」的なことかと.
Releaseにしてopencv_world452を使ってみたら解決しました!
教えていただいたみなさん、特に解決していただいたjbpb0さん、ありがとうございました!
> 特に解決していただいたjbpb0さん
実質解決したのはyominetさんです
早くから
> Debugモードでやってるとかはないでしょうか?
と聞いてましたし、使ってるlibファイル名をコピペで貼れって書いたのもyominetさんだし
とりあえずめでたしめでたしですね。
ライブラリがデバッグ版とかお茶目な前提だと、もう時間測定以前だったりしますが、一応念の為ウンチクだけ書いておきます。
処理時間はエッジ検出量で大きく変わるようです。今回でいうと、HoughCircle()のparam1と使用した動画の兼ね合いですね。
Win10+VS2019+vcpkgという構成で64bitRelease版を作成して
https://www.videezy.com/backgrounds/14400-circles-diagram
の動画を処理させたらparam1=20だと固まったかのごとく遅かったです。無意味に解像度高いのもあって、1フレーム分の処理が数分待っても終わりません。param1=100にすると1フレーム1秒以内くらいにはなってくれました。何が言いたいかというと、Pythonと比較するならパラメータくらい合わせないとまるで結果が違うときがありますよ、ということです。
そのまま動くソースもあった方がいいだろうし、ほとんど変えてないし汚いけど、ご参考までにそのまま載せときます。
# include <iostream>
# include <opencv2/opencv.hpp>
# include <opencv2/videoio.hpp>
# include <opencv2/core/core.hpp>
# include <opencv2/imgproc/imgproc.hpp>
# include <opencv2/highgui/highgui.hpp>
using namespace cv;
using namespace std;
#define WINDOW_NAME "Video"
int main() {
cv::Mat img, frame, gray, gau, canny;
cv::namedWindow(WINDOW_NAME, cv::WINDOW_NORMAL);
// https://www.videezy.com/backgrounds/14400-circles-diagram (ここから頂きました)
cv::VideoCapture cap("C:\\diagram_circles.mp4"); //このように、動画ファイルのパスを指定する
int v_w = cap.get(cv::CAP_PROP_FRAME_WIDTH); //縦の大きさ
int v_h = cap.get(cv::CAP_PROP_FRAME_HEIGHT); //横の大きさ
int max_frame = cap.get(cv::CAP_PROP_FRAME_COUNT); //フレーム数
int fps = cap.get(cv::CAP_PROP_FPS); //フレームレート
cout << "v_w=" << v_w << endl;
cout << "v_h=" << v_h << endl;
cout << "fps=" << fps << endl;
cout << "max_frame=" << max_frame << endl;
// 時間調整用に100フレームでやめる
const int MAX_FRAME = 100;
max_frame = (max_frame > MAX_FRAME) ? MAX_FRAME : max_frame;
cv::TickMeter meterForHough;
for (int CFN = 0; CFN < max_frame; CFN++) {
cap >> frame; //1フレーム分取り出してframeに保持させる
img = frame;
cv::cvtColor(img, gray, COLOR_BGR2GRAY);
cv::GaussianBlur(gray, gau, cv::Size(11, 11), 2, 2);
//Canny処理単体目視確認パラメータ調整用
//cv::Canny(gau, canny, 100, 50);
//cv::Canny(gau, canny, 20, 10);
// Hough変換による円の検出と検出した円の描画
std::vector<cv::Vec3f> circles;
meterForHough.start();
//cv::HoughCircles(gau, circles, HOUGH_GRADIENT, 1, 100, 20, 50);
cv::HoughCircles(gau, circles, HOUGH_GRADIENT, 1, 300, 100, 40, 200, 0);
meterForHough.stop();
std::vector<cv::Vec3f>::iterator i = circles.begin();
for (; i != circles.end(); ++i) {
cv::Point center(cv::saturate_cast<int>((*i)[0]), cv::saturate_cast<int>((*i)[1]));
int radius = cv::saturate_cast<int>((*i)[2]);
cv::circle(frame, center, radius, cv::Scalar(255, 0, 0), 2);
cv::circle(frame, center, 3, cv::Scalar(255, 0, 0), -1);
}
cv::imshow("Video", frame);
//cv::imshow("Video", canny);
int key = cv::waitKey(1); // 表示のために最低1ms待つ
if (key == 27) { break; }// esc or enterキーで終了
}
cout << "avg_meter(hough): " << meterForHough.getAvgTimeMilli() << "[ms]" << endl;
}
あなたの回答
tips
プレビュー