用 Go 實現圖片尺寸的自動調節

來源:互聯網
上載者:User
這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。

我剛上大學那會兒,課上到最後幾分鐘的時候,我會翹課奔到另外一個我幾乎不怎麼瞭解的班上去蹭課。碰巧,那個班上的課是我覺得最棒的課之一 ——電腦視覺。此外,那個課上介紹了一種很贊的演算法:Seam Carving,精雕細琢。

這個演算法大概是醬紫的:一般我們想改變圖片大小的時候,會採用裁剪和縮放的方式,這樣一來,圖片會損失很多重要訊息,在處理過程中,圖片甚至被歪曲。那麼,我們怎麼才能找到圖片中視覺資訊最少的部分,要調整圖片大小的時候,只把這部分移除掉是不是可以呢?

展示給我們一副很美的畫面:開闊的藍天,俊逸的城堡。但是,對我們來說,圖有點大,我們得往小調一下。怎麼弄呢?

coraaller
翻譯於 3周前

2人頂

 翻譯的不錯哦!

第一個進入我們大腦的想法是改變原始映像的尺寸。改變之後的映像(如)變小了,而且所有的主要資訊(左邊的人,右邊的城堡)都還在新的映像上。但是,改變後的映像有一個問題,右邊城堡變形了,所以這張改變之後的映像就顯得不太完美了。在大部分情況下,這種映像的改變是可以接受的,但是如果我們試圖提供一個高品質的映像的話,這種改變就不能接受了。

另外一個想法是切掉原始映像的一部分以適應我們新的尺寸(如)。基本上我們可以理解發現新的映像有一個致命的缺點,一半的城堡被切掉了,而且左邊的人現在也太靠近映像的邊緣。相對於原始映像,新的映像確實包含了大部分原始映像的資訊,但是同時也丟失了很多的重要訊息。我個人就比較喜歡城堡右邊的那個炮樓,希望在新的映像中可以保留這個炮樓。幸運的是,我們可以做到這點。

讓我們看上面的映像,映像的尺寸已經減小了,在新的映像中,城堡是完整的,並且左邊的人也不再位於映像的邊緣。上面的這張新的映像是經過一個叫做Seam Carving的演算法進行處理過的。這個演算法將動態監測原始映像,發現原始映像中不太重要的部分,並且將這部分不太重要的映像有限切除掉。在上面的新的映像中,你可以發現這個演算法把城堡右邊的藍天給切除了,而且還切除了部分位於原始映像中間部分的藍天。

HAILINCAI
翻譯於 3周前

0人頂

 翻譯的不錯哦!

其它翻譯版本(1)
正在載入...

它是如何確定哪些地區應該首先去掉呢?我們通過研究一個Go語言實現的演算法來找到答案。我們研究演算法的各個步驟,以及每一步對下面的圖片產生的效果。這個演算法雖然是用來減少映像高度的,但是也可以很容易地修改用來減小映像的寬度。

該演算法包含了三個主要的步驟: 從原圖產生能量圖、 定位找出最低能量消耗的 “seam" , 將找出的”seam“從映像中去除.

// ReduceHeight 使用seam carving演算法,減少給定的具有n個像素點的映像的高度.func ReduceHeight(im image.Image, n int) image.Image {    energy := GenerateEnergyMap(im)    seam := GenerateSeam(energy)    return RemoveSeam(im, seam)}

能量圖計算映像中的一個點包含了多少“能量”,也就是說該點包含了多少資訊。低能量的像素同周圍像素融合在一起,去掉它們對整個圖的影響比較小。因此能量圖的計算,採用了考慮映像水平和垂直的梯度值的方法來進行。通過這種方法產生的能量圖,其中每個點代表了原始映像中的對應點與周邊點相似或不同的程度。
幸運的是,這可以通過一個特定的濾波器(這裡採用一個sobel濾波器)對輸入映像進行卷積計算。我在這裡不詳細討論卷積,但要知道一個重要的事情是,通過將sobel濾波器應用到一個輸入映像的灰階映像上(通常可選的平滑濾波器,如高斯濾波器,來獲得灰階映像),我們可以很容易地獲得的輸入映像的梯度。要做到這一點我使用了功能強大的GIFT庫。

// GenerateEnergyMap 應用輸入灰階和sobel濾波器到輸入映像上,產生能量圖.func GenerateEnergyMap(im image.Image) image.Image {    g := gift.New(gift.Grayscale(), gift.Sobel())    res := image.NewRGBA(im.Bounds())    g.Draw(res, im)    return res}

hxapp2
翻譯於 3周前

0人頂

 翻譯的不錯哦!

正如所期望的,高能量的地區一般都是邊緣,低能量的地區均是由少量類比色彩(天空)擴充而來的。從這裡我們可以估計到,減少圖片的高度,減少的部分應該大部分都是在天空地區,其他部分保持不變。

下一步決定哪些像素需要進行移除。我們將一個像素一個像素的減少映像的高度,就需要一列一列的找那個像素能夠移除。我們希望找到一系列的具有儘可能最低總能量的像素集合,移除掉這些seam,對整個圖片產生影響最小。可以按如下兩步來確定最佳去除像素點:

// GenerateSeam 返回最優的可以消除的水平 seam.func GenerateSeam(im image.Image) Seam {    mat := GenerateCostMatrix(im)    return FindLowestCostSeam(mat)}

第一步是用一個八連通地區像素去水平濾波整個映像,獲得包含“seams"的最低積累能量的消耗矩陣。

這次我們首先看如下代碼:

// GenerateCostMatrix 從映像左端到每一個像素,建立一個表明最低消耗seam矩陣.//// mat[x][y] 是從映像左端到列x行y像素點的seam的累積能量.func GenerateCostMatrix(im image.Image) [][]float64 {    min, max := im.Bounds().Min, im.Bounds().Max    height, width := max.Y-min.Y, max.X-min.X    mat := make([][]float64, width)    for x := min.X; x < max.X; x++ {        mat[x-min.X] = make([]float64, height)    }    // Initialize first column of matrix    for y := min.Y; y < max.Y; y++ {        e, _, _, a := im.At(0, y).RGBA()        mat[0][y-min.Y] = float64(e) / float64(a)    }    updatePoint := func(x, y int) {        e, _, _, a := im.At(x, y).RGBA()        up, down := math.MaxFloat64, math.MaxFloat64        left := mat[x-1][y]        if y != min.Y {            up = mat[x-1][y-1]        }        if y < max.Y-1 {            down = mat[x-1][y+1]        }        val := math.Min(float64(left), math.Min(float64(up), float64(down)))        mat[x][y] = val + (float64(e) / float64(a))    }    // Calculate the remaining columns iteratively    for x := min.X + 1; x < max.X; x++ {        for y := min.Y; y < max.Y; y++ {            updatePoint(x, y)        }    }    return mat}
hxapp2
翻譯於 2周前

0人頂

 翻譯的不錯哦!

在上面的函數中,我們開始建立一個同映像具有相同維數的矩陣。我們從最左列到最右列,不斷的計算每一個像素的最低累積能量。在一列中的每一個像素,選取其左邊或者左上或者左下三個點中最小積累能量的點,然後將該點的能量累加到選取的點的積累能量上。這種做法,使得我們能夠不是那麼死板的只能線性移除seam,帶來更大的靈活性,獲得更好的清除效果。

然後我們就可以利用這個矩陣來確定哪些像素可以被移除。我們從一個包含每列一個點的seam開始,找到最小成本的seam的開始。

type Seam []Pointtype Point struct {    X, Y int}// FindLowestCostSeam uses an cost matrix to find the optimal seam for removal.func FindLowestCostSeam(mat [][]float64) Seam {    width, height := len(mat), len(mat[0])    seam := make([]Point, width)    min, y := math.MaxFloat64, 0    for ind, val := range mat[width-1] {        if val < min {            min = val            y = ind        }    }    seam[width-1] = Point{X: width - 1, Y: y}

然後我們從右至左遍曆矩陣。每一次迴圈遍曆,查看該點、以及其上和其下三點,將最小的積累能力賦值給該seam。

for x := width - 2; x >= 0; x-- {    left := mat[x][y]    up, down := math.MaxFloat64, math.MaxFloat64    if y > 0 {        up = mat[x][y-1]    }    if y < height-1 {        down = mat[x][y+1]    }    if up <= left && up <= down {        seam[x] = Point{X: x, Y: y - 1}        y = y - 1    } else if left <= up && left <= down {        seam[x] = Point{X: x, Y: y}        y = y    } else {        seam[x] = Point{X: x, Y: y + 1}        y = y + 1    }}

我們通過在映像上畫出seam來可視化的檢查我們的程式邏輯,確認了seam是通過了我們所期待的地區。下面的映像是上面代碼產生的第一個seam,用紅色線畫在輸入映像上。

hxapp2
翻譯於 2周前

0人頂

 翻譯的不錯哦!

因此演算法就是通過編寫一個函數,該函數建立一個新的去掉了計算出來的seam的映像,並且將ReduceHeight函數放到一個迴圈中去,我們就可以不斷的通過消除最小能量的seam來放大縮小一個映像。

// RemoveSeam creates a copy of the provided image, with the pixels at // the points in the provided seam removed.func RemoveSeam(im image.Image, seam Seam) image.Image {    b := im.Bounds()    out := image.NewRGBA(image.Rect(0, 0, b.Dx(), b.Dy()-1))    min, max := b.Min, b.Max    for _, point := range seam {        x := point.X        for y := min.Y; y < max.Y; y++ {            if y == point.Y {                continue            }            if y > point.Y {                out.Set(x, y-1, im.At(x, y))            } else {                out.Set(x, y, im.At(x, y))            }        }    }    return out}// ReduceHeight uses seam carving to reduce height of given image n pixels.func ReduceHeight(im image.Image, n int) image.Image {    for x := 0; x < n; x++ {        energy := GenerateEnergyMap(im)        seam := GenerateSeam(energy)        im = RemoveSeam(im, seam)    }    return im}

在這裡是清除了50個像素後的效果。我們可以看到,哪些具有最少資訊的地區(天空)已經清除掉,而哪些有船,水和建築的地區沒有改變。因為天空基本上是一致的,清除這些地區沒有太大的影響。

最終的實現代碼可以在 Github 上找到,所有函數均能被匯出,可以按你想要的方式去研究修改。

hxapp2
翻譯於 2周前

0人頂

 翻譯的不錯哦!

這篇文章只是淺顯地介紹了 seam 的裁剪。關於這個話題,我強烈推薦你閱讀原創的論文,或者視頻,這些演算法證明了許多應用的可能性。這些應用程式套件括對象移除,增加映像的尺寸,或者更多。

這不是說 seam 裁剪沒有警告。正如上面的資源連結中的討論那樣,這可以探索許多不同功能的函數,這種處理方法處理具有非常嚴格的空間關係(例如人類的臉)的圖片是非常困難的。這意味著這些東西可以被避免,但在這裡可以先不討論這些。

如果你有任何評論或關於這篇文章的問題,請聯絡我們,進行演算法或其他你關心的任何討論。

無若
翻譯於 3周前

0人頂

 翻譯的不錯哦!

本文中的所有譯文僅用於學習和交流目的,轉載請務必註明文章譯者、出處、和本文連結
我們的翻譯工作遵照 CC 協議,如果我們的工作有侵犯到您的權益,請及時聯絡我們

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.