原博客:http://blog.csdn.net/linshanxian/article/details/68944748
旋轉有一個繞着什麼轉的問題。通常的做法是以圖像的中心爲圓心旋轉,將圖像上的所有像素都旋轉一個相同的角度。圖像的旋轉變換是圖像的位置變換,但旋轉後圖像的大小一般會改變。和平移變換一樣,既可以把轉出顯示區域的圖像截去,也可以擴大顯示區域以顯示完整的圖像,如下圖所示。
我們先討論不裁剪轉出部分,擴大顯示區域的情況。在下圖所示的平面座標系中,A0逆時針旋轉θ變成A1,r是該點到原點的距離,則旋轉前:
旋轉後A1的座標爲
寫成矩陣的形式爲:
其逆變換矩陣如下:
上面公式是旋轉變換的基本公式,座標系是以圖像的中心爲原點,向右爲x軸正方向,向上爲y軸正方向。上述旋轉是繞座標原點進行的,如果是繞指定點(a,b)旋轉,那麼應該先將座標系平移至改點,再旋轉,然後平移至新的座標原點。
下面推導座標系平移的變換公式。座標系Ⅰ是圖像的座標系,座標系Ⅱ是旋轉座標系,座標系Ⅱ的原點在座標系中爲(a,b),如下圖所示。
兩種座標系之間的轉換爲:
逆變換爲:
有了上面的公式,就可以很方便的推導圖像旋轉變換的表達式。假設圖像未旋轉時候旋轉中心的座標是(a,b),旋轉後中心點的座標爲(c,d)(在新的座標系下,以旋轉後圖像的左上角爲原點),則可以把變換分爲3步:
第一步,將座標系Ⅰ變成Ⅱ;
第二步,旋轉θ(逆時針爲正,順時針爲負);
第三步,將座標系Ⅱ變換回Ⅰ。這樣就得到了總的變換矩陣。
設原圖像某像素點的座標爲(x0,y0),旋轉後在目標圖像的座標爲(x1,y1),則旋轉變換的矩陣表達式爲:
逆變換爲:
有了上面的轉換公式,就可以很方便的編寫出實現圖像旋轉的程序。首先需要計算出公式中需要的幾個參數:a、b、c、d和旋轉後圖像的尺寸。已知原是圖像的寬度爲w0,高度爲h0,以圖像的中心爲座標原點。則原圖像四個角的座標分別是:
按照旋轉公式,旋轉後這四個點的座標分別是:
則新圖像的高度和寬度分別爲:
令
圖像旋轉的主要代碼如下:
void RotIamge(const Mat &srcImage, Mat &dstImage, double angle)
{
//弧度
double sita = angle * CV_PI / 180;
double a = (srcImage.cols - 1) / 2.0;
double b = (srcImage.rows - 1) / 2.0;
int srcRow = srcImage.rows;
int srcCol = srcImage.cols;
double x1 = -a * cos(sita) - b * sin(sita);
double y1 = -a * sin(sita) + b * cos(sita);
double x2 = a * cos(sita) - b * sin(sita);
double y2 = a * sin(sita) + b * cos(sita);
double x3 = a * cos(sita) + b * sin(sita);
double y3 = a * sin(sita) - b * cos(sita);
double x4 = -a * cos(sita) + b * sin(sita);
double y4 = -a * sin(sita) - b * cos(sita);
int w1 = cvRound(max(abs(x1 - x3), abs(x4 - x2)));
int h1 = cvRound(max(abs(y1 - y3), abs(y4 - y2)));
dstImage.create(h1, w1, srcImage.type());
double c = (w1 - 1) / 2.0;
double d = (h1 - 1) / 2.0;
double f1 = -c * cos(sita) + d * sin(sita) + a;
double f2 = -c * sin(sita) - d * sin(sita) + b;
int nRowNum = dstImage.rows;
int nColNum = dstImage.cols;
for (int i = 0; i < nRowNum; i++)
{
for (int j = 0; j < nColNum; j++)
{
int x = cvRound(j * cos(sita) - i * sin(sita) + f1);
int y = cvRound(j * sin(sita) + i * cos(sita) + f2);
if (x > 0 && x < srcCol && y > 0 && y < srcRow)
{
dstImage.at<Vec3b>(i, j) = srcImage.at<Vec3b>(y, x);
}
}
}
}
對於旋轉以後圖像大小不變的情況,旋轉前後圖像的中心點座標都是(a,b),那麼旋轉的變換矩陣就是:
逆變換爲:
公式中,
主要代碼如下:
void RotIamge2(const Mat &srcImage, Mat &dstImage, double angle)
{
//弧度
double sita = angle * CV_PI / 180;
double a = (srcImage.cols - 1) / 2.0 + 0.5;
double b = (srcImage.rows - 1) / 2.0 + 0.5;
int nRowNum = srcImage.rows;
int nColNum = srcImage.cols;
dstImage.create(nRowNum, nColNum, srcImage.type());
double f1 = -a * cos(sita) + b * sin(sita) + a;
double f2 = -a * sin(sita) - b * cos(sita) + b;
for (int i = 0; i < nRowNum; i++)
{
for (int j = 0; j < nColNum; j++)
{
int x = cvRound(j * cos(sita) - i * sin(sita) + f1);
int y = cvRound(j * sin(sita) + i * cos(sita) + f2);
if (x > 0 && x < nColNum && y > 0 && y < nRowNum)
{
dstImage.at<Vec3b>(i, j) = srcImage.at<Vec3b>(y, x);
}
}
}
}
要注意的是,由於有浮點運算,計算出來的座標可能不是整數,需要採取取整處理,使用cvRound()函數尋找最接近的點,這樣會帶來一些誤差,圖像可能會出現鋸齒,更好的方式是採用插值,後續將會具體介紹。