[問題描述]
在撰寫一個特別的演算法時,
需要只針對某一行做運算,因此使用cv::Rect做了一個ROI,
將某一行取出做運算,但發現我不太能修改ROI取出的影像的值。
由於程式龐大,且涉及公司機密,所以寫了一個測試用的小程式重現
#include <iostream>
#include <opencv2/opencv.hpp>
int main()
{
cv::Mat mat = cv::Mat::ones(5, 5, CV_8UC1);
cv::Mat mat2;
cv::Rect ROI = cv::Rect(3, 0, 1, 5);
std::cout << "mat = \n" << mat << std::endl;
mat2 = mat(ROI);
std::cout << "before change, mat2 = \n" << mat2 << std::endl;
for (int i = 0; i < mat2.total(); i++){
*(mat2.data + i) = 0;
}
std::cout << "after changed, mat2 = \n" << mat2 << std::endl;
system("pause");
return 1;
}
以下是執行結果:
mat =
[ 1, 1, 1, 1, 1;
1, 1, 1, 1, 1;
1, 1, 1, 1, 1;
1, 1, 1, 1, 1;
1, 1, 1, 1, 1]
test error version!
mat2 can't changed any value
before change, mat2 =
[ 1;
1;
1;
1;
1]
after changed, mat2 =
[ 0;
1;
1;
1;
1]
請按任意鍵繼續 . . .
ROI影像,除了第一個值被改變外,其餘的值都沒法被改變!
[解題思路]
一開始以為此錯誤的起因是影像的記憶體位置不是一列,而是一行,
所以想用reshape讓它變一列。
(我當時也覺得這種想法很荒謬,但當時完全沒有這個錯誤相關的想法,所以先假設一個「有一點點可能的」 [3] )
cv::Mat mat2 = mat.reshape(0, 1).clone();
但遇到了「The matrix is not continuous, thus its number of rows can not be changed」
查了資料時,看到了這篇《CvMat执行CvReshape()报错"The matrix is not continuous, thus its number of rows can not be changed"》,
裡面提到「 重新生成一个全新的矩阵,具有相同的数据,那么数据就是连续的,就能执行reshape()函数操作。」
此時,我突然想到之前讀的另一篇文章《C++ Opencv 傅里叶变换的代码实现及关键函数详解》
裡面有提到copyto和clone的不同,在於會不會分配一段新的記憶體位置。
此時,我突然想到:「會不會cv::Mat(cv::Rect) 這樣的ROI,並不是複製,
而是類似於『標記、顯示在原圖的哪個位置』的作用,所以有保護機制,會將原圖的記憶體咬住,不讓別人存取(access)!?」
後來加上clone就解決了,這也表示:
如果用了 cv::Mat(cv::Rect) 這樣的ROI,由於是原圖的某個部分,所以記憶體位置是分散的,並非像重新宣告一個記憶體空間,是連續的
[解決方法]
在建使用cv::Mat(cv::Rect)時,調用其clone()方法!
因為cv::Mat(cv::Rect)和copyTo一樣是淺拷貝(shallow copy),
只會複制原cv::Mat的標頭而已,
換句話說,新的cv::Mat的指標,是指向原cv::Mat的記憶體位置。
所以要搭配clone()方法,才可以達成深拷貝(deep-copy),
額外向系統要一個記憶體空間。
這樣操作的時候,才不會操作到原本的cv::Mat。
修改後的程式碼如下:
#include <iostream>
#include <opencv2/opencv.hpp>
int main()
{
cv::Mat mat = cv::Mat::ones(5, 5, CV_8UC1);
cv::Mat mat2;
cv::Rect ROI = cv::Rect(3, 0, 1, 5);
std::cout << "mat = \n" << mat << std::endl;
mat2 = mat(ROI).clone();
std::cout << "before change, mat2 = \n" << mat2 << std::endl;
for (int i = 0; i < mat2.total(); i++){
*(mat2.data + i) = 0;
}
std::cout << "after changed, mat2 = \n" << mat2 << std::endl;
system("pause");
return 1;
}
執行結果
mat =
[ 1, 1, 1, 1, 1;
1, 1, 1, 1, 1;
1, 1, 1, 1, 1;
1, 1, 1, 1, 1;
1, 1, 1, 1, 1]
before change, mat2 =
[ 1;
1;
1;
1;
1]
after changed, mat2 =
[ 0;
0;
0;
0;
0]
請按任意鍵繼續 . . .
整理一下opencv淺拷貝(shallow copy)及深拷貝(deep copy):
- 淺拷貝(shallow copy)
- 雖然一樣是建一個新的cv::Mat,但成員的部分都是與舊的一樣;換句話說,影像資料是將指標指向原本的cv::Mat。
- opencv淺拷貝的方法或函數
- cv::Mat的方法
- copyTo(cv::Mat)
- cv::Mat(cv::Mat)
- cv::Mat(cv::Rect)
- reshape
- 深拷貝(deep copy)
- 建立一個全新的cv::Mat類別實體,並將原本類別的資料複制過去,影像資料也是宣告一個新的記憶體空間,並將原本影像資料複制過來。
- opencv深拷貝的方法或函數
- cv::Mat的方法
- clone()
好像cv::Mat除了clone()外都是淺拷貝吔!囧
因為我想記錄一下我的思考過程,發現的原因如下(有點長,慎入!):
其實我一開始寫的小程式是這樣:
#include <iostream>
#include <opencv2/opencv.hpp>
int main()
{
cv::Mat mat = cv::Mat::ones(3, 4, CV_8UC1);
cv::Mat mat2;
cv::Rect ROI = cv::Rect(1, 1, 3, 2);
std::cout << "mat = \n" << mat << std::endl;
std::cout << "\ntest error version!\n mat2 can\'t changed any value" << std::endl;
mat2 = mat(ROI);
std::cout << "before change, mat2 = \n" << mat2 << std::endl;
for (int i = 0; i < mat2.total(); i++){
*(mat2.data + i) = 0;
}
std::cout << "after changed, mat2 = \n" << mat2 << std::endl;
system("pause");
return 1;
}
執行結果:
mat =
[ 1, 1, 1, 1;
1, 1, 1, 1;
1, 1, 1, 1]
test error version!
mat2 can't changed any value
before change, mat2 =
[ 1, 1, 1;
1, 1, 1]
after changed, mat2 =
[ 0, 0, 0;
0, 0, 1]
請按任意鍵繼續 . . .
才發現,其實也不是完全不能改變其值,
但的確會有問題!
由於一開始是使用vim + mingw + cmake去編譯的,
我還以為是編譯器的問題(因為算法的專案用是使用visual studio 2015),
結果用VS也寫一個,發現結果一樣,才確定不是編譯器的問題!
剛解決完時,我還是沒想到為什麼要加clone。
後來我突然想到一件事:
cv::Mat(cv::Rect)是不是像是copyTo方法一樣,
只是把標頭給新的cv::Mat而已,所以我就把程式改回錯的版本,
並在修改mat2後,把mat顯示出來,果然驗證了我的想法!
程式如下:
#include <iostream>
#include <opencv2/opencv.hpp>
int main()
{
cv::Mat mat = cv::Mat::ones(5, 5, CV_8UC1);
cv::Mat mat2;
cv::Rect ROI = cv::Rect(3, 0, 1, 5);
std::cout << "mat = \n" << mat << std::endl;
//mat2 = mat(ROI).clone();
mat2 = mat(ROI);
std::cout << "before change, mat2 = \n" << mat2 << std::endl;
for (int i = 0; i < mat2.total(); i++){
*(mat2.data + i) = 0;
}
std::cout << "after changed, mat2 = \n" << mat2 << std::endl;
std::cout << "mat = \n" << mat << std::endl;
system("pause");
return 1;
}
執行結果如下:
mat =
[ 1, 1, 1, 1, 1;
1, 1, 1, 1, 1;
1, 1, 1, 1, 1;
1, 1, 1, 1, 1;
1, 1, 1, 1, 1]
before change, mat2 =
[ 1;
1;
1;
1;
1]
after changed, mat2 =
[ 0;
1;
1;
1;
1]
mat =
[ 1, 1, 1, 0, 0;
0, 0, 0, 1, 1;
1, 1, 1, 1, 1;
1, 1, 1, 1, 1;
1, 1, 1, 1, 1]
請按任意鍵繼續 . . .
所以要搭配clone()方法,才可以達成深拷貝(deep-copy),
額外向系統要一個記憶體空間,這樣操作的時候,才不會操作到原本的cv::Mat。
後來我針對我這個想法,找了一些相關資料:[4]~[6]
參考資料:
[3] 軟體除錯技巧
這封郵件來自 Evernote。Evernote 是您專屬的工作空間,免費下載 Evernote |
沒有留言:
張貼留言