Golang图像处理:学习如何进行图片的高清化和去马赛克

1. 简介

在现代社会,图片处理已经是十分常见的操作了,例如图片的放大、去噪等。Go语言具有如此快速、高效的特性,也允许使用底层API进行详细的图像处理操作,完全可以使用Go实现图像高清化、去噪等操作。本文将介绍如何使用Golang进行图像处理,实现图像的高清化和去马赛克化操作。

2. 图像高清化

2.1 图像边缘检测

图像高清化分为两个主要阶段:边缘检测和插值。边缘检测是找到图像的边缘区域,也就是明暗变化处,其中常用于处理的算法有Sobel算法和Canny算法。本文使用Sobel算法进行边缘检测,实现代码如下:

import "image"

// 生成一张灰度图片

func toGrayScale(img image.Image) *image.Gray {

bounds := img.Bounds()

w, h := bounds.Max.X, bounds.Max.Y

grayImg := image.NewGray(bounds)

for x := 0; x < w; x++ {

for y := 0; y < h; y++ {

r, g, b, _ := img.At(x, y).RGBA()

gray := float64(0.299*float64(r) + 0.587*float64(g) + 0.114*float64(b))

grayImg.SetGray(x, y, color.Gray{uint8(gray / 257)})

}

}

return grayImg

}

// 使用Sobel算子进行边缘检测

func SobelFilter(img *image.Gray) *image.Gray {

bounds := img.Bounds()

w, h := bounds.Max.X, bounds.Max.Y

dx, dy := image.NewGray(bounds), image.NewGray(bounds)

for x := 1; x < w-1; x++ {

for y := 1; y < h-1; y++ {

dxv := (-1)*img.GrayAt(x-1, y-1).Y + 1*img.GrayAt(x+1, y-1).Y +

(-2)*img.GrayAt(x-1, y).Y + 2*img.GrayAt(x+1, y).Y +

(-1)*img.GrayAt(x-1, y+1).Y + 1*img.GrayAt(x+1, y+1).Y

dyv := -1*img.GrayAt(x-1, y-1).Y + (-2)*img.GrayAt(x, y-1).Y + -1*img.GrayAt(x+1, y-1).Y +

1*img.GrayAt(x-1, y+1).Y + 2*img.GrayAt(x, y+1).Y + 1*img.GrayAt(x+1, y+1).Y

dx.SetGray(x, y, color.Gray{uint8(dxv / 4)})

dy.SetGray(x, y, color.Gray{uint8(dyv / 4)})

}

}

out := image.NewGray(bounds)

for x := 1; x < w-1; x++ {

for y := 1; y < h-1; y++ {

vx := int(dx.GrayAt(x, y).Y)

vy := int(dy.GrayAt(x, y).Y)

gray := uint8(math.Sqrt(float64(vx*vx + vy*vy)))

out.SetGray(x, y, color.Gray{gray})

}

}

return out

}

上述代码通过toGrayScale函数将彩色图像转为灰度图像,然后调用SobelFilter函数进行边缘检测。

2.2 图像插值

插值是将图像从低分辨率升级到高分辨率的过程。最经典的插值算法是双线性插值法,具体实现如下:

func BilinearScale(srcImg image.Image, dstW, dstH int) *image.RGBA {

bounds := srcImg.Bounds()

srcW, srcH := bounds.Max.X, bounds.Max.Y

scaleX, scaleY := float64(srcW)/float64(dstW), float64(srcH)/float64(dstH)

dstImg := image.NewRGBA(image.Rect(0, 0, dstW, dstH))

for x := 0; x < dstW; x++ {

for y := 0; y < dstH; y++ {

srcX, srcY := (float64(x)+0.5)*scaleX-0.5, (float64(y)+0.5)*scaleY-0.5

x0, y0 := int(math.Floor(srcX)), int(math.Floor(srcY))

x1, y1 := x0+1, y0+1

dx, dy := srcX-float64(x0), srcY-float64(y0)

tl := toRGBA(srcImg.At(x0, y0))

tr := toRGBA(srcImg.At(x1, y0))

bl := toRGBA(srcImg.At(x0, y1))

br := toRGBA(srcImg.At(x1, y1))

r := float64(tl.R)*(1-dx)*(1-dy) + float64(tr.R)*dx*(1-dy) + float64(bl.R)*(1-dx)*dy + float64(br.R)*dx*dy

g := float64(tl.G)*(1-dx)*(1-dy) + float64(tr.G)*dx*(1-dy) + float64(bl.G)*(1-dx)*dy + float64(br.G)*dx*dy

b := float64(tl.B)*(1-dx)*(1-dy) + float64(tr.B)*dx*(1-dy) + float64(bl.B)*(1-dx)*dy + float64(br.B)*dx*dy

a := float64(tl.A)*(1-dx)*(1-dy) + float64(tr.A)*dx*(1-dy) + float64(bl.A)*(1-dx)*dy + float64(br.A)*dx*dy

dstImg.SetRGBA(x, y, color.RGBA{uint8(r + 0.5), uint8(g + 0.5), uint8(b + 0.5), uint8(a + 0.5)})

}

}

return dstImg

}

func toRGBA(c color.Color) color.RGBA {

if v, ok := c.(color.RGBA); ok {

return v

}

r, g, b, a := c.RGBA()

return color.RGBA{uint8(r / 0x101), uint8(g / 0x101), uint8(b / 0x101), uint8(a / 0x101)}

}

上述代码通过BilinearScale函数进行图像插值,其中函数内部调用toRGBA函数将颜色转换为RGBA类型。

2.3 组合边缘检测和图像插值

通过上述代码,我们已经能够获得边缘检测和图像插值两个序列。我们只需要将这两个序列组合在一起,就能得到高清化后的图像了。完整的高清化代码如下:

import "image/jpeg"

func HD(filename string) *image.RGBA {

file, err := os.Open(filename)

if err != nil {

log.Fatal(err)

}

defer file.Close()

img, err := jpeg.Decode(file)

if err != nil {

log.Fatal(err)

}

grayImg := toGrayScale(img)

sobelImg := SobelFilter(grayImg)

hdImg := BilinearScale(sobelImg, img.Bounds().Max.X, img.Bounds().Max.Y)

return hdImg

}

上述代码通过调用toGrayScale函数、SobelFilter函数和BilinearScale函数,完成了高清化操作。

3. 图像去马赛克化

3.1 K均值聚类算法

图像去马赛克化是令图片变得“块状”,因此我们需要使用典型的聚类算法,用于将原图中相近的像素组合在一起。本文使用K均值聚类算法对图像进行去马赛克化。 K均值算法通过不断迭代,将数据分为K个类别。每个类别有一个中心点,表示属于该类别的所有样本的平均值。因此,该算法通过迭代来优化分组中心点,以最小化组内的“距离”之和,这是一种典型的无监督学习算法。

3.2 K均值聚类算法实现

下面的代码展示了如何使用Go语言实现K均值聚类算法。

import "encoding/binary"

const (

iterations = 10

k = 16 // k 个中心点

)

type Cluster struct { // 类别的结构体

sum [3]float32 // 颜色的R G B 三个通道的总和

members int // 该类别的像素数量

}

// 获取所有像素的RBG通道的值

func GetImageColorVector(filename string) []Vector {

file, err := os.Open(filename)

if err != nil {

panic(err)

}

defer file.Close()

img, err := jpeg.Decode(file)

if err != nil {

panic(err)

}

bounds := img.Bounds()

width, height := bounds.Max.X, bounds.Max.Y

out := make([]Vector, width*height)

i := 0

for y := 0; y < height; y++ {

for x := 0; x < width; x++ {

r, g, b, _ := img.At(x+bounds.Min.X, y+bounds.Min.Y).RGBA()

out[i][0] = float32(r)

out[i][1] = float32(g)

out[i][2] = float32(b)

i++

}

}

return out

}

// 针对 K 中心点找到相应的类别

func mapToCluster(data Vector, clusters []Cluster) int {

minIndex := 0

minValue := Distance(data, Vector(clusters[0].sum))/float32(clusters[0].members)

for i := 1; i < k; i++ {

testValue := Distance(data, Vector(clusters[i].sum)) / float32(clusters[i].members)

if testValue < minValue {

minIndex = i

minValue = testValue

}

}

return minIndex

}

// K 均值聚类算法

func Kmeans(filenames []string, dstFilename string) {

// 初始化中心点

clusters := make([]Cluster, k)

vectors := make([]Vector, 0, k*1024)

for i := range clusters {

clusters[i].members = 1

for j := range clusters[i].sum {

clusters[i].sum[j] = rand.Float32() * 255

}

vectors = append(vectors, Vector(clusters[i].sum))

}

// 处理所有像素的值

for _, filename := range filenames {

data := GetImageColorVector(filename)

for i := range data {

vector := data[i]

index := mapToCluster(vector, clusters)

for j := range clusters[index].sum {

clusters[index].sum[j] += vector[j]

}

clusters[index].members++

}

}

// 进行多轮迭代

for n := 0; n < iterations; n++ {

// 重置聚类

for i := range clusters {

for j := range clusters[i].sum {

clusters[i].sum[j] = 0

}

clusters[i].members = 0

}

// 针对每个像素点找到得到最近中心点

for _, filename := range filenames {

data := GetImageColorVector(filename)

for i := range data {

vector := data[i]

index := mapToCluster(vector, clusters)

for j := range clusters[index].sum {

clusters[index].sum[j] += vector[j]

}

clusters[index].members++

}

}

// 重置中心点

for i := range clusters {

if clusters[i].members <= 1 {

continue

}

for j := range clusters[i].sum {

clusters[i].sum[j] /= float32(clusters[i].members)

}

vectors[i] = Vector(clusters[i].sum)

}

}

// 从 K 块得到平均值并写回磁盘

for _, filename := range filenames {

file, err := os.Open(filename)

if err != nil {

panic(err)

}

defer file.Close()

img, err := jpeg.Decode(file)

if err != nil {

panic(err)

}

bounds := img.Bounds()

width, height := bounds.Max.X, bounds.Max.Y

outImg := image.NewRGBA(image.Rect(0, 0, width, height))

for y := 0; y < height; y++ {

for x := 0; x < width; x++ {

r, g, b, _ := img.At(x+bounds.Min.X, y+bounds.Min.Y).RGBA()

data := Vector{float32(r), float32(g), float32(b)}

index := mapToCluster(data, clusters)

for i := range clusters[index].sum {

clusters[index].sum[i] /= float32(clusters[index].members)

}

outImg.Set(x, y, color.RGBA{

uint8(clusters[index].sum[0] + 0.5),

uint8(clusters[index].sum[1] + 0.5),

uint8(clusters[index].sum[2] + 0.5),

uint8(255),

})

}

}

outFilename := dstFilename + "_" + path.Base(filename)

out, err := os.Create(outFilename)

if err != nil {

panic(err)

}

defer out.Close()

err = jpeg.Encode(out, outImg, &jpeg.Options{Quality: 95})

if err != nil {

panic(err)

}

}

}

// 数据类型

type Vector [3]float32

// 两个数据之间的欧式距离

func Distance(a, b Vector) float32 {

diff := Vector{a[0] - b[0], a[1] - b[1], a[2] - b[2]}

return float32(math.Sqrt(float64(diff[0]*diff[0] + diff[1]*diff[1] + diff[2]*diff[2])))

}

// 随机值

var rand = rand.New(rand.NewSource(uint64(time.Now().UnixNano())))

// 将α Set为全255

func init() {

for i := range MaxUint16 {

MaxUint16[i] = 65535

}

}

上述代码先调用GetImageColorVector函数将所有像素点从图像中提取出来,并将它们存储在向量空间中。接着在给定K值的情况下,分配初始点集并通过迭代来优化平均值。可能有些像素没有被分配到任何一组,因此在计算平均像素时需要注意这一问题。

根据算法的优化,将颜色均分为16个等级,可以看到原始图像像素明显减少。

4. 结论

通过本文的介绍,我们知道如何使用Golang进行图像高清化和去马赛克。本文介绍的高清化算法是通过Sobel算法进行边缘检测,然后使用双线性插值法进行图像插值;去马赛克则是使用K均值聚类算法实现。

希望本文的介绍能够帮助大家更好地理解图像处理算法。当然,图像高

后端开发标签