話說,平凡之處顯真格,這一點也沒錯! 比如,對旋轉映像進行雙線性插值,很簡單吧? 可,對我,折騰了大半天,也沒有達到預期效果! 尤其是三個誤區讓我抓瞎好久:
1,座標旋轉公式。 這東西,要用的時候查資料,抄過來,從不記清,猛地一下讓人寫正確,確實不容易,雖然只是正餘弦的排列問題。畫圖推導的方法也是知道,但是,奈何又記不得三角形的和角展開公式。沒辦法,只好逐一測實驗證了,心血經驗,45、90,135,180這幾個角度最好都驗證一下。
2,雙插的資料來源。 一開始,思維上習慣地資料來源認定應該是旋轉之後的,為此施展多種手段都不能較好克服資料有效性、配對性等異常。搞個帶掩模的3*3濾波吧,卻使映像變模糊了。 絕境反思,資料來源取自源圖資料,該是多好的事呀。 仿射變換 warpAffine() 函數中的仿射矩陣就是預設為逆向。
3,雙插的方法。 一直來,都知道X、Y方向要各插值一次,但卻不明確它們的相互關係是 串列,而非並行!
以下貼出我後來完善出的旋轉部分代碼,有路過的高手請幫忙指點最佳化一下:
//旋轉 平移 點座標,依據旋轉矩陣而來void rotatePoint(const Point2d& src, Point2d& dst, const double angle, const Point2d& offset=Point2f(0,0)){ const double cosAngle =cos(angle); const double sinAngle =sin(angle); dst.x = src.x * cosAngle + src.y * sinAngle + offset.x; dst.y = src.y * cosAngle - src.x * sinAngle + offset.y;}//旋轉 平移 點座標 angle中的x值為 cos(angle) y為sin(angle)inline void rotatePoint(const Point2d& src, Point2d& dst, const Point2d& angle, const Point2d& offset=Point2f(0,0)){ //dst.x = src.dot(angle) + offset.x; dst.x = src.x * angle.x + src.y * angle.y + offset.x; dst.y = src.y * angle.x - src.x * angle.y + offset.y;}//雙線性插值 a為左上點 b右上 c左下 d右下 權重因子Sx Sy 的取值範圍為(0 , 1)由小座標指向大座標距離比template<typename T> inline double insertDLine(const T a, const T b, const T c, const T d, const double Sx, const double Sy){ const double Sx1 = 1 -Sx; const double Sy1 = 1 -Sy; return (a *Sx1 *Sy1 + b *Sx *Sy1 + c *Sx1 *Sy + d *Sx *Sy);}//雙線性插值 dst為data映像中的2*2子塊 權重因子Sx Sy 的取值範圍為(0 , 1)由小座標指向大座標距離比void insertDLine(const Mat& src, Scalar& dst,const double Sx, const double Sy){ const int channels =src.channels(); const uchar *pU = src.ptr(0); const uchar *pD = src.ptr(1); const int depth =min(4, channels); for (int i = 0; i < depth; i++) { dst[i] = insertDLine(pU[i], pU[i + channels], pD[i], pD[i + channels], Sx, Sy); }}//將源映像旋轉一定的角度 int rotateImage(const Mat& src, Mat& dst, double angle, const bool isDegree){ const int channels =src.channels(); if(channels > 4) { //dst=src; // return -1; } //將角度化為弧度 if(isDegree) { angle *=CV_PI/180; } //參數初始化 const double cosAngle =cos(angle); const double sinAngle =sin(angle); const int srcRows = src.rows; const int srcCols = src.cols; const int dstCols =srcRows *abs(sinAngle) + srcCols *abs(cosAngle); const int dstRows =srcRows *abs(cosAngle) + srcCols *abs(sinAngle); const int srcRowsLess2 = srcRows -2; const int srcColsLess2 = srcCols -2; const Point2d centerA(srcCols/2 +0.5, srcRows/2 +0.5); const Point2d centerB(dstCols/2 +0.5, dstRows/2 +0.5); const Point2d rotateAngle(cosAngle, -sinAngle); //用於從靶心圖表迴旋轉到初始圖,角度取反 Point2d hitPoint; int xL, yL; Rect insertROI(0, 0, 2, 2); Scalar insertVaule; //申請記憶體空間,並設定為 0 dst.create(dstRows, dstCols, CV_8UC(channels)); dst.setTo(Scalar(0,0,0,0)); for (int i = 0; i < dstRows; i++) { uchar *pDst=dst.ptr(i); for (int j = 0; j < dstCols; j ++) { rotatePoint(Point2d(j, i)-centerB, hitPoint, rotateAngle, centerA); xL =floor(hitPoint.x); yL =floor(hitPoint.y); //從靶心圖表中迴轉至源映像,不在地區內的直接跳過 if (xL < 0 || xL > srcColsLess2 || yL < 0 || yL > srcRowsLess2) { continue; } insertROI.x =xL; insertROI.y =yL; insertDLine(src(insertROI), insertVaule, hitPoint.x -xL, hitPoint.y -yL); int base =j *channels; for (int z = 0; z < channels; z++) { pDst[base +z] = insertVaule[z]; } } } return 0;}
為了展示我的手寫旋轉函數rotateImage() 與仿射變換 warpAffine() 函數的效果比較,有如下程式碼片段:
double angle =90.0 * CV_PI/180; //將角度化為弧度 30 Mat rotateImg; rotateImage(colorImg, rotateImg, angle); const double cosAngle =cos(angle); const double sinAngle =sin(angle); Mat rrM =(Mat_<double>(2,3) << cosAngle, sinAngle, cosAngle *colorImg.rows, -sinAngle, cosAngle, sinAngle *colorImg.cols); Mat rotatewarpAffine; warpAffine(colorImg, rotatewarpAffine, rrM, colorImg.size()*2);
旋轉30度時:
rotateImage30
warpAffine 30
旋轉90度時:
rotateImage90.jpg
warpAffine 90