數字圖像處理——圖像的幾何變換
幾何變換不改變像素值,而是改變像素所在的位置。
它包括兩個獨立的算法:
- 空間變換算法
- 插值算法
分類
- 從圖像類型上
- 二維圖像
- 三維圖像
- 從三維到二維平面投影變換
- 從變換的性質
- 基本變換:平移,比例縮放,旋轉,鏡像,錯切
- 復合變換
圖像的平移
在同一坐標系下,設\(P_0(x_0,y_0)\) ,經過水平偏移量\(\triangle x\) ,垂直偏移量\(\triangle y\),得到平移之后的坐標:
用矩陣變換表示為:
求逆?不妨將偏移量直接取負。
我們把變換矩陣求逆之后可以觀察一下:
其實也就是
圖像的鏡像
- 水平鏡像
- 垂直鏡像
設圖像寬度為\(Width\),高度為\(Height\)
那么水平鏡像的坐標變化為
變換矩陣:
求逆:
void translateTransformSize(Mat const &src, Mat & dst, int dx, int dy){
int rows = src.rows + dx;
int cols = src.cols + dy;
dst.create(rows, cols, src.type());
for (int i = 0; i < rows; i++){
for (int j = 0; j < cols; j++){
int x0 = i - dx;
int y0 = j - dy;
if (x0 >= 0 && y0 >= 0 && x0 < src.cols && y0 < src.rows)
dst.at<Vec3b>(i, j) = src.at<Vec3b>(x0, y0);
}
}
}
圖像的縮放
將給定圖像在 \(x\) 軸方向按比例縮放\(f_x\) 倍,在\(y\) 軸方向按比例縮放\(f_y\)倍,從而獲得一副新的圖像。
- 若 \(f_x = f_y\) ,則這樣的比例縮放為圖像的全比例縮放。
- 若\(f_x != f_y\) , 則產生幾何畸變
坐標的縮放變化:
其反變換:
void zoom(Mat& src, Mat &dst, double sx, double sy){
// 縮放之后的大小
int rows = (src.rows * sx + 0.5);
int cols = (src.cols * sy + 0.5);
dst.create(rows, cols, src.type());
Vec3b * p;
for (int i = 0; i < rows; i++){
int row = (i / sx + 0.5);
if (row >= src.rows)
row--;
Vec3b *origin = src.ptr<Vec3b>(row);
p = dst.ptr<Vec3b>(i);
for (int j = 0; j < cols; j++){
int col = (j / sy + 0.5);
if (col >= src.cols)
col--;
p[j] = origin[col];
}
}
}
圖像的旋轉
設順時針旋轉\(\theta\) 角后對應點為\(P(x,y)\)
矩陣變換:
其逆運算矩陣為
以上是以旋轉中心為原點的坐標系下,坐標的變換,在實際圖像中,我們一般是以某個點作為旋轉中心來進行旋轉的,而原圖的坐標系是以左上角為坐標原點的,所以我們要通過轉換坐標系來將坐標調整到以旋轉中心為原點的坐標系中
假設在原坐標系下坐標為\((x',y')\) ,在旋轉坐標系下坐標為\((x,y)\),旋轉中心在原坐標系下為\((a_0,b_0)\)
矩陣變換:
其逆變換:
現在求P在原坐標系旋轉前坐標\((x_0,y_0)\)和旋轉后坐標\((x,y)\)的關系。其中\((c,d)\)為旋轉后的坐標中心,\((a,b)\)為在原坐標系中的旋轉中心
接下來的問題是,如何確定旋轉之后的坐標中心?
很容易想到,圖像旋轉之后,圖像的大小會變(如果要顯示完整圖像),而旋轉中心是始終在中心的,所以我們可以通過計算四個角旋轉后的坐標,確定旋轉之后的圖片大小,進而確定旋轉之后的坐標中心(左上角)
已知旋轉前的旋轉中心坐標:
通過上面的旋轉坐標計算,我們很容易的可以求出來四個角旋轉之后的坐標,分別是:\((x1',y1'),(x2',y2'),(x3',y3'),(x4',y4')\)。則旋轉后圖像寬度和高度為:
請讀者想一下這里為什么這么計算寬度和高度?
進而可以得知旋轉之后的坐標中心
//該函數是將point順時針旋轉 angle
Point2d coordinates(Point2d point, double angle){
const double cosAng = cos(angle);
const double sinAng = sin(angle);
int x = point.x;
int y = point.y;
int x1 = x * cosAng + y * sinAng;
int y1 = -x * sinAng + y * cosAng;
Point2d res = Point2d(x1, y1);
return res;
}
//旋轉主體函數
void rotate(Mat& src, Mat& dst, Point2d center, double angle){
const double cosAng = cos(angle);
const double sinAng = sin(angle);
//請留意這里的坐標運算
Point2d leftTop(-center.x, center.y); //(0,0)
Point2d rightTop(src.cols - center.x, center.y); // (width,0)
Point2d leftBottom(-center.x, -src.rows + center.y); //(0,height)
Point2d rightBottom(src.cols - center.x, -src.rows + center.y); // (width,height)
//以center為中心旋轉后四個角的坐標
Point2d transLeftTop, transRightTop, transLeftBottom, transRightBottom;
transLeftTop = coordinates(leftTop, angle);
transRightTop = coordinates(rightTop, angle);
transLeftBottom = coordinates(leftBottom, angle);
transRightBottom = coordinates(rightBottom, angle);
double left = min({ transLeftTop.x, transRightTop.x, transLeftBottom.x, transRightBottom.x });
double right = max({ transLeftTop.x, transRightTop.x, transLeftBottom.x, transRightBottom.x });
double top = max({ transLeftTop.y, transRightTop.y, transLeftBottom.y, transRightBottom.y });
double down = min({ transLeftTop.y, transRightTop.y, transLeftBottom.y, transRightBottom.y });
//得到新圖像的寬度和高度
int width = (fabs(left - right) + 0.5);
int height = (fabs(top - down) + 0.5);
dst.create(width, height, src.type());
//num1與num2是為了方便矩陣運算(因為每次計算都有這兩個值)
const double num1 = -abs(left) * cosAng - abs(top) * sinAng + center.x;
const double num2 = abs(left) * sinAng - abs(top) * cosAng + center.y;
Vec3b *p;
for (int i = 0; i < height; i++)
{
p = dst.ptr<Vec3b>(i);
for (int j = 0; j < width; j++)
{
int x = static_cast<int>(j * cosAng + i * sinAng + num1 + 0.5);
int y = static_cast<int>(-j * sinAng + i * cosAng + num2 + 0.5);
if (x >= 0 && y >= 0 && x < src.cols && y < src.rows)
p[j] = src.ptr<Vec3b>(y)[x];
}
}
}
圖像的錯切
錯切之后
x方向的錯切
y方向的錯切
初次總結,還有很多的不足,請各位看客多多包涵,小生會更加努力學習!