[英]How can I make this faster? (C/C++) OpenCV
我正在處理視頻中的幀並實時顯示(實時)。 該算法速度很快,但是我想知道是否可以進行優化以使其更加無縫。 我不知道算法中的哪些函數會占用最多的時間,我的猜測是sqrt()函數,因為顯然它會進行一些查找,但我不確定。
這是我的算法:
IplImage *videoFrame = cvCreateImage(cvSize(bufferWidth, bufferHeight), IPL_DEPTH_8U, 4);
videoFrame->imageData = (char*)bufferBaseAddress;
int channels = videoFrame->nChannels;
int widthStep = videoFrame->widthStep;
int width = videoFrame->width;
int height = videoFrame->height;
for(int i=0;i<height;i++){
uchar *col = ((uchar *)(videoFrame->imageData + i*widthStep));
for(int j=0;j<width;j++){
double pRed = col[j*channels + 0];
double pGreen = col[j*channels + 1];
double pBlue = col[j*channels + 2];
double dRed = green.val[0] - pRed;
double dGreen = green.val[1] - pGreen;
double dBlue = green.val[2] - pBlue;
double sDRed = dRed * dRed;
double sDGreen = dGreen * dGreen;
double sDBlue = dBlue * dBlue;
double sum = sDRed + sDGreen + sDBlue;
double euc = sqrt(sum);
//NSLog(@"%f %f %f", pRed, pGreen, pBlue);
if (euc < threshold) {
col[j*channels + 0] = white.val[0];
col[j*channels + 1] = white.val[1];
col[j*channels + 2] = white.val[2];
}
}
}
謝謝!
更新好的,所以這樣做是循環遍歷圖像中的每個像素,並計算像素顏色和綠色之間的歐幾里得距離。 因此,總體而言,這是一個綠屏算法。
我做了一些基准測試,未使用此算法的fps為30.0fps。 使用此算法,它下降到大約8fps。 但是,大多數for drop都來自col[j*channels + 0];
如果該算法沒有做任何其他事情,並且使用了數組選擇的訪問權限,則它會下降到大約10fps。
更新2好吧,這很有趣,我從double循環內的內容中刪除了隨機行,以查看導致更大開銷的原因,這就是我發現的:在堆棧上創建變量會導致FPS大幅下降。 考慮以下示例:
for(int i=0;i<height;i++){
uchar *col = ((uchar *)(data + i*widthStep));
for(int j=0;j<width;j++){
double pRed = col[j*channels + 0];
double pGreen = col[j*channels + 1];
double pBlue = col[j*channels + 2];
}
}
這會使fps下降到11位。
現在,另一方面:
for(int i=0;i<height;i++){
uchar *col = ((uchar *)(data + i*widthStep));
for(int j=0;j<width;j++){
col[j*channels + 0];
col[j*channels + 1];
col[j*channels + 2];
}
}
根本不會降低FPS! FPS保持在30.0左右。 以為我應該更新它,讓你們知道這是什么真正的瓶頸,而不是堆積變量。 我想知道我是否可以內聯所有內容,以獲得30.0fps的幀率。
Nvm ...也許未分配給var的表達式甚至都沒有求值。
sqrt
是單調遞增的函數,您似乎只在閾值測試中使用它。
由於單調性, sqrt(sum) < threshold
等於sum < threshold * threshold
(假設閾值為正)。
不再需要昂貴的平方根,並且編譯器會將乘法移出循環。
下一步,您可以從內部循環中刪除昂貴的乘法j * channels
。 編譯器應該足夠聰明,只能執行一次並使用3次結果,但其余計算所依賴的結果仍然是乘數,因此會影響流水線化。
還記得乘法與重復加法相同嗎? 通常,執行更多操作會更昂貴,但是在這種情況下,由於循環的原因,您已經具有重復部分。 因此使用:
for(int j=0;j<width;j++){
double pRed = col[0];
double pGreen = col[1];
double pBlue = col[2];
double dRed = green.val[0] - pRed;
double dGreen = green.val[1] - pGreen;
double dBlue = green.val[2] - pBlue;
double sDRed = dRed * dRed;
double sDGreen = dGreen * dGreen;
double sDBlue = dBlue * dBlue;
double sum = sDRed + sDGreen + sDBlue;
//NSLog(@"%f %f %f", pRed, pGreen, pBlue);
if (sum < threshold * threshold) {
col[0] = white.val[0];
col[1] = white.val[1];
col[2] = white.val[2];
}
col += channels;
}
接下來,您需要在uchar
和double
之間進行昂貴的轉換。 閾值測試不需要這些:
int j = width;
do {
int_fast16_t const pRed = col[0];
int_fast16_t const pGreen = col[1];
int_fast16_t const pBlue = col[2];
int_fast32_t const dRed = green.val[0] - pRed;
int_fast32_t const dGreen = green.val[1] - pGreen;
int_fast32_t const dBlue = green.val[2] - pBlue;
int_fast32_t const sDRed = dRed * dRed;
int_fast32_t const sDGreen = dGreen * dGreen;
int_fast32_t const sDBlue = dBlue * dBlue;
int_fast32_t const sum = sDRed + sDGreen + sDBlue;
//NSLog(@"%f %f %f", pRed, pGreen, pBlue);
if (sum < threshold * threshold) {
col[0] = white.val[0];
col[1] = white.val[1];
col[2] = white.val[2];
}
col += channels;
} while (--j);
過早的優化始終是一件壞事-如果確實有必要,則需要有確鑿的證據作為后盾。 在幾乎所有情況下,編譯器都能很好地優化代碼的細節-降低高級功能的復雜度是您的工作。
與其嘗試優化這段特定的代碼,不如先檢查您的性能在程序的其他地方是否處於瓶頸,然后檢查是否可以完全避免首先調用此函數。 只有在確定除了優化該代碼之外別無其他事情之后,您才應該開始考慮優化該代碼。
如果您真的真的必須優化此代碼,則最好的方法是使用MMX和SIMD指令將所有雙重“三元組”向量化為單個指令。
好吧,在不知道您的算法做什么的情況下,如果您想對其進行一點改進,就可以擺脫該sqrt
調用。 只需替換:
double euc = sqrt(sum);
if (euc < threshold) {
....
}
通過:
if (sum < threshold_2) {
....
}
其中threshold_2
等於threshold * threshold
,您可以預先計算並退出循環。
這樣可以提高性能,但不要期望過高。
sqrt
是相當緩慢。 為什么不計算double threshold_sq = threshold * threshold;
在外循環之前,使用sum < threshold_sq
進行比較。 此外, restrict
關鍵字可能會也可能不會有所幫助。
我建議您研究像Valgrind這樣的東西。 它具有許多有用的測試,可以測試幾乎所有的代碼。
根據您的評論, col[j*channels + 0];
花費很多時間: channels
總是3個嗎? 甚至總是4? 如果是這樣,您可以通過僅前進指針來避免偏移數學,如下所示:
for(int i=0;i<height;i++){
uchar *col = ((uchar *)(videoFrame->imageData + i*widthStep));
for(int j=0;j<width;j++){
double dRed = green.val[0] - *col++;
double dGreen = green.val[1] - *col++;
double dBlue = green.val[2] - *col++;
//math here
if (euc < thresholdSqrd) {
*(col-3) = white.val[0];
*(col-2) = white.val[1];
*(col-1) = white.val[2];
}
col++; //do this only if `channels`==4
}
另外,由於原始數據似乎是連續字節的rgb,因此可以使用*(int32_t*)(col-3) |= 0xFFFFFF;
將像素設置為白色*(int32_t*)(col-3) |= 0xFFFFFF;
並且將您的減為整數可能會稍快一些(將green
存儲為ints):
int16_t iRed = green.val[0] - *col++;
int16_t iGreen = green.val[1] - *col++;
int16_t iBlue = green.val[2] - *col++;
double euc = (double)iRed*iRed + iGreen*iGreen + iBlue*iBlue;
如果您使用Linux,請查看oprofile和實用程序perf(隨內核源代碼提供)。
順便說一句,UPDATE2中的代碼可能根本不做任何事情,它被編譯出來了,因為經驗的影響沒有存儲在任何地方。 在這種情況下,編譯器決定根本不將其放入輸出中。 用-S(匯編器輸出)編譯代碼,然后看一看。
您正在使用嵌套的for loops
但我完全看不到您使用外部循環中的變量。 如果所寫的內容實際上是正確的,我建議您修改外部for loop
,這會將運行時間從O(n^2)
更改為O(n)
。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.