OpenCV2:圖像的幾何變換,平移、鏡像、縮放、旋轉(1)

圖像的幾何變換是在不改變圖像內容的前提下對圖像像素的進行空間幾何變換,主要包括了圖像的平移變換、鏡像變換、縮放和旋轉等。本文首先介紹了圖像幾何變換的一些基本概念,而後再OpenCV2下實現了圖像的平移變換、鏡像變換、縮放以及旋轉,最後介紹幾何的組合變換(平移+縮放+旋轉)。算法

1.幾何變換的基本概念

1.1 座標映射關係

圖像的幾何變換改變了像素的空間位置,創建一種原圖像像素與變換後圖像像素之間的映射關係,經過這種映射關係可以實現下面兩種計算:spa

  1. 原圖像任意像素計算該像素在變換後圖像的座標位置
  2. 變換後圖像的任意像素在原圖像的座標位置

對於第一種計算,只要給出原圖像上的任意像素座標,都能經過對應的映射關係得到到該像素在變換後圖像的座標位置。將這種輸入圖像座標映射到輸出的過程稱爲「向前映射」。反過來,知道任意變換後圖像上的像素座標,計算其在原圖像的像素座標,將輸出圖像映射到輸入的過程稱爲「向後映射」。可是,在使用向前映射處理幾何變換時卻有一些不足,一般會產生兩個問題:映射不徹底,映射重疊3d

  1. 映射不徹底
    輸入圖像的像素總數小於輸出圖像,這樣輸出圖像中的一些像素找不到在原圖像中的映射。
    image
    上圖只有(0,0),(0,2),(2,0),(2,2)四個座標根據映射關係在原圖像中找到了相對應的像素,其他的12個座標沒有有效值。
  2. 映射重疊
    根據映射關係,輸入圖像的多個像素映射到輸出圖像的同一個像素上。
    image
    上圖左上角的四個像素(0,0),(0,1),(1,0),(1,1)都會映射到輸出圖像的(0,0)上,那麼(0,0)究竟取那個像素值呢?

要解決上述兩個問題可使用「向後映射」,使用輸出圖像的座標反過來推算改座標對應於原圖像中的座標位置。這樣,輸出圖像的每一個像素均可以經過映射關係在原圖像找到惟一對應的像素,而不會出現映射不徹底和映射重疊。因此,通常使用向後映射來處理圖像的幾何變換。從上面也能夠看出,向前映射之因此會出現問題,主要是因爲圖像像素的總數發生了變化,也就是圖像的大小改變了。在一些圖像大小不會發生變化的變換中,向前映射仍是頗有效的。code

 

1.2.插值算法

對於數字圖像而言,像素的座標是離散型非負整數,可是在進行變換的過程當中有可能產生浮點座標值。例如,原圖像座標(9,9)在縮小一倍時會變成(4.5,4.5),這顯然是一個無效的座標。插值算法就是用來處理這些浮點座標的。常見的插值算法有最鄰近插值法、雙線性插值法,二次立方插值法,三次立方插值法等。本文主要介紹最鄰近插值和雙線性插值,其餘一些高階的插值算法,之後再作研究。orm

  1. 最鄰近插值
    也被稱爲零階插值法,最簡單插值算法,固然效果也是最差的。它的思想至關簡單,就是四捨五入,浮點座標的像素值等於距離該點最近的輸入圖像的像素值。
    image
    上面的代碼能夠求得(x,y)的最鄰近插值座標(u,v)。
    最鄰近插值幾乎沒有多餘的運算,速度至關快。可是這種鄰近取值的方法是很粗糙的,會形成圖像的馬賽克、鋸齒等現象。
  2. 雙線性插值
    它的插值效果比最鄰近插值要好不少,相應的計算速度也要慢上很多。雙線性插值的主要思想是計算出浮點座標像素近似值。那麼要如何計算浮點座標的近似值呢。一個浮點座標一定會被四個整數座標所包圍,將這個四個整數座標的像素值按照必定的比例混合就能夠求出浮點座標的像素值。混合比例爲距離浮點座標的距離。
    假設要求座標爲(2.4,3)的像素值P,該點在(2,3)和(3,3)之間,以下圖
    image u和v分別是距離浮點座標最近兩個整數座標像素在浮點座標像素所佔的比例
    P(2.4,3) = u * P(2,3) + v * P(3,3),混合的比例是以距離爲依據的,那麼u = 0.4,v = 0.6。
    上面是隻在一條直線的插值,稱爲線性插值。雙線性插值就是分別在X軸和Y軸作線性插值運算。
    下面利用三次的線性插值進行雙線性插值運算
    image
    (2.4,3)的像素值 F1 = m * T1 + (1 – m) * T2
    (2.4,4)的像素值 F2 = m * T3 + (1 – m ) * T4
    (2.4,3.5)的像素值 F = n * F1 + (1 – n) * F2
    這樣就能夠求得浮點座標(2.4,3.5)的像素值了。
    求浮點座標像素F,設該浮點座標周圍的4個像素值分別爲T1,T2,T3,T4,而且浮點座標距離其左上角的橫座標的差爲m,縱座標的差爲n。
    F1 = m * T1 + (1 – m) * T2
    F2 = m * T3 +  (1 – m) *T4
    F = n * F1 + (1 – n) * F2
    上面就是雙線性插值的基本公式,能夠看出,計算每一個像素像素值須要進行6次浮點運算。並且,因爲浮點座標有4個座標近似求得,若是這個四個座標的像素值差異較大,插值後,會使得圖像在顏色分界較爲明顯的地方變得比較模糊。

2.圖像平移

圖像的平移變換就是將圖像全部的像素座標分別加上指定的水平偏移量和垂直偏移量。平移變換根據是否改變圖像大小分爲兩種
imageimage
左邊平移圖像的大小發生了,在保證圖像平移的同時,也保存了完整的圖像信息。右邊的平移圖像大小沒有變化,故圖像右下角的部分被截除了。
blog

2.1平移變換原理

設dx爲水平偏移量,dy爲垂直偏移量,(x0,y0)爲原圖像座標,(x,y)爲變換後圖像座標,則平移變換的座標映射爲
image
get

這是向前映射,即將原圖像的座標映射到變換後的圖像上。
其逆變換爲
image,向後映射,即將變換後的圖像座標映射到原圖像上。在圖像的幾何變換中,通常使用向後映射。
it

2.2 基於OpenCV的實現

圖像的平移變換實現仍是很簡單的,這裏再也不贅述.form

平移後圖像的大小不變
class

void GeometricTrans::translateTransform(cv::Mat const& src, cv::Mat& dst, int dx, int dy)
{
    CV_Assert(src.depth() == CV_8U);

    const int rows = src.rows;
    const int cols = src.cols;

    dst.create(rows, cols, src.type());

    Vec3b *p;
    for (int i = 0; i < rows; i++)
    {
        p = dst.ptr<Vec3b>(i);
        for (int j = 0; j < cols; j++)
        {
            //平移後坐標映射到原圖像
            int x = j - dx;
            int y = i - dy;

            //保證映射後的座標在原圖像範圍內
            if (x >= 0 && y >= 0 && x < cols && y < rows)
                p[j] = src.ptr<Vec3b>(y)[x];
        }
    }
}

平移後圖像的大小變化

void GeometricTrans::translateTransformSize(cv::Mat const& src, cv::Mat& dst, int dx, int dy)
{
    CV_Assert(src.depth() == CV_8U);

    const int rows = src.rows + abs(dy); //輸出圖像的大小
    const int cols = src.cols + abs(dx);

    dst.create(rows, cols, src.type());
    Vec3b *p;
    for (int i = 0; i < rows; i++)
    {
        p = dst.ptr<Vec3b>(i);
        for (int j = 0; j < cols; j++)
        {
            int x = j - dx;
            int y = i - dy;

            if (x >= 0 && y >= 0 && x < src.cols && y < src.rows)
                p[j] = src.ptr<Vec3b>(y)[x];
        }
    }
}

ps:這裏圖像變換的代碼以三通道圖像爲例,單通道的於此相似,代碼中沒有作處理。

3.圖像的鏡像變換

圖像的鏡像變換分爲兩種:水平鏡像和垂直鏡像。水平鏡像以圖像垂直中線爲軸,將圖像的像素進行對換,也就是將圖像的左半部和右半部對調。垂直鏡像則是以圖像的水平中線爲軸,將圖像的上半部分和下班部分對調。效果以下:
imageimage

3.1變換原理

設圖像的寬度爲width,長度爲height。(x,y)爲變換後的座標,(x0,y0)爲原圖像的座標

  1. 水平鏡像變換
    image向前映射
    其逆變換爲
    image向後映射
  2. 垂直鏡像變換
    image
    其逆變換爲
    image

3.2基於OpenCV的實現

水平鏡像的實現

void GeometricTrans::hMirrorTrans(const Mat &src, Mat &dst)
{
    CV_Assert(src.depth() == CV_8U);
    dst.create(src.rows, src.cols, src.type());

    int rows = src.rows;
    int cols = src.cols;

    switch (src.channels())
    {
    case 1:
        const uchar *origal;
        uchar *p;
        for (int i = 0; i < rows; i++){
            origal = src.ptr<uchar>(i);
            p = dst.ptr<uchar>(i);
            for (int j = 0; j < cols; j++){
                p[j] = origal[cols - 1 - j];
            }
        }
        break;
    case 3:
        const Vec3b *origal3;
        Vec3b *p3;
        for (int i = 0; i < rows; i++) {
            origal3 = src.ptr<Vec3b>(i);
            p3 = dst.ptr<Vec3b>(i);
            for(int j = 0; j < cols; j++){
                p3[j] = origal3[cols - 1 - j];
            }
        }
        break;
    default:
        break;
    }
    
}

分別對三通道圖像和單通道圖像作了處理,因爲比較相似之後的代碼只處理三通道圖像,再也不作特別說明。

在水平鏡像變換時,遍歷了整個圖像,而後根據映射關係對每一個像素都作了處理。實際上,水平鏡像變換就是將圖像座標的列換到右邊,右邊的列換到左邊,是能夠以列爲單位作變換的。一樣垂直鏡像變換也如此,能夠以行爲單位進行變換。

垂直鏡像變換

void GeometricTrans::vMirrorTrans(const Mat &src, Mat &dst)
{
    CV_Assert(src.depth() == CV_8U);
    dst.create(src.rows, src.cols, src.type());

    int rows = src.rows;

    for (int i = 0; i < rows; i++)
        src.row(rows - i - 1).copyTo(dst.row(i));
}
src.row(rows - i - 1).copyTo(dst.row(i));

上面一行代碼是變換的核心代碼,從原圖像中取出第i行,並將其複製到目標圖像。

困了頂不住了啊,寫理論部分太痛苦了啊,明天繼續幾何變換的後續幾種:轉置、縮放、旋轉以及組合變換。

相關文章
相關標籤/搜索