Go语言之图像处理

golang的image库提供了图像处理基本功能

一、语法:

1、image库的Rect()函数可用于创建一个矩形对象,语法如下:

func Rect(x0, y0, x1, y1 int) Rectangle

其中,x0, y0 为左上角坐标,x1, y1 为右下角坐标。这个函数返回一个 Rectangle 类型的对象,表示以指定坐标为左上角和右下角所构成的矩形,例如:r := image.Rect(0, 0, 100, 50) 表示创建一个从左上角坐标 (0, 0) 到右下角坐标 (100, 50) 的矩形,将其保存在 r 变量中,Rectangle结构体实际是Point结构体嵌套而来,因此image.Rect(0,0,100,50)还可以写为image.Rectangle{image.Point{0,0},image.Point{100,50}},如图:

注:Golang的图像坐标系中,Y轴向下是正方向,X轴向右是正方向

2、image库的NewRGBA() 函数用于创建一个 RGBA 类型的图像对象,语法如下:

func NewRGBA(r Rectangle) *RGBA

其中,r 表示图像的大小,是一个 Rectangle 类型的对象。这个函数返回一个指向 RGBA 类型的图像对象的指针

例如:创建一个宽为100、高为50的 RGBA 类型的图像对象,结合第一步的Rect()函数,如下:

r := image.Rect(0, 0, 100, 50)
img := image.NewRGBA(r)

上面代码会创建一个由 100×50 个像素点组成,每个像素点有红、绿、蓝和透明度四个通道的 RGBA 类型的图像对象,并将其保存在 img 变量中,创建完图像对象后,可以通过设置每个像素点的颜色值来绘制图像

3、image/color库的RGBA结构体是一个代表 RGB 颜色模型中的一种颜色的结构体。它由四个 8 位无符号整数组成,分别表示红、绿、蓝和 alpha(透明度)通道的值。具体定义如下:

type RGBA struct {
    R, G, B, A uint8
}

其中R、G、B分别表示红、绿、蓝三原色的颜色值,其中 0 表示完全透明,255 表示完全不透明,color.RGBA 结构体实现了 color.Color 接口,可以在图像绘制等场景中使用

4、draw库Draw()函数是一个用于在目标图像上绘制源图像的函数。它可以实现将一个图像粘贴到另一个图像上的功能,支持多种不同的操作模式和混合模式。使用方式如下:

import "image"
import "image/draw"
// 创建目标图像和源图像
dst := image.NewRGBA(image.Rect(0, 0, 100, 100))
src := image.NewUniform(color.RGBA{255, 0, 0, 255})
// 在 dst 上绘制 src
draw.Draw(dst, dst.Bounds(), src, image.Point{}, draw.Src)

这将在 dst 图像上绘制一个红色图形,大小为 dst 的边界框(本例中为 [0,0,100,100])。draw.Draw() 函数的第一个参数是目标图像对象,第二个参数是绘制区域的矩形边界框,第三个参数是源图像对象,第四个参数是开始绘制的位置,第五个参数draw.Src表示绘制模式,有多个参数,如下:

  • Over: 默认操作,直接将源图像绘制到目标图像
  • Src: 只绘制源图像的颜色,忽略目标图像的颜色 
  • Dst: 只保留目标图像的颜色,忽略源图像的颜色
  • Clear: 用透明色填充目标图像

5、image库的RGBA结构体的Bounds()方法用于返回该图像对象的边界框。具体定义如下:

func (p *RGBA) Bounds() Rectangle

Bounds() 方法返回的是一个 image.Rectangle 类型的对象,表示该图像对象的边界框。这个边界框的左上角坐标为 (0, 0),右下角坐标为 (w, h),其中 w 和 h 分别为图像的宽度和高度(以像素为单位)。例如,如果我们有一个名为 imgimage.RGBA 对象,可以通过以下方式获取其边界框:

bounds := img.Bounds()

这将返回一个 image.Rectangle 类型的对象 bounds,可以通过调用其 MinMax 方法获取其左上角和右下角的坐标。例如,要获取图像的宽度和高度,可以使用以下代码:

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

6、image库的NewUniform()函数用于创建一个包含单一颜色的图像对象。它接受一个 color.Color 类型的参数作为输入,返回一个实现了 image.Image 接口的新图像对象,用法如下:

import "image"
import "image/color"

// 创建一个 100x100 的红色图像
c := color.RGBA{255, 0, 0, 255}
img := image.NewUniform(c)
#注意:image.Rectangle{image.Point{0, 0}, image.Point{100, 100}}等价于image.Rect(0,0,100,100)
dst := image.NewRGBA(image.Rectangle{image.Point{0, 0}, image.Point{100, 100}})
draw.Draw(dst, dst.Bounds(), img, image.Point{}, draw.Src)

上述代码将创建一个大小为 1×1,颜色为 {255, 0, 0, 255}(红色)的 color.RGBA 对象,并将其传递给 image.NewUniform() 函数,以创建一个包含该颜色的新图像对象。在这个例子中,我们使用了 draw.Draw() 函数将该图像绘制到另一个图像上

7、image库的 Point 是一个表示二维平面上的点的结构体。它由两个整数类型的 X 和 Y 坐标组成,分别表示点在水平和垂直方向的位置。具体定义如下:

type Point struct {
    X, Y int
}

image.Point 可以用于表示图像的像素坐标、矩形的左上角和右下角坐标等位置信息

例如,我们可以使用 image.Point 来指定一个矩形的左上角和右下角坐标,然后使用该矩形作为参数调用 image.NewRGBA() 函数创建一个 RGBA 图像对象:

import "image"

// 创建一个 100x100 的 RGBA 图像
rect := image.Rectangle{image.Point{0, 0}, image.Point{100, 100}}
img := image.NewRGBA(rect)

上述代码将创建一个大小为 100×100 的 RGBA 图像对象,并且左上角坐标为 (0,0),右下角坐标为 (100,100)

二、实战:

1、下面例子表示创建一个背景为白色的矩形图像,如图:

  • 13行:首先通过image.Rect()函数创建长300高300的矩形对象,并通过NewRGBA创建图像对象
  • 14行:创建一个空图片,返回一个File指针和error
  • 18行:通过bufio.NewWriter()初始化一个Writer结构体对象,返回bufio.Writer结构体指针
  • 19行:通过png.Encode()将图像对象newimage编码为png格式数据,如果读取文件就要解码(Decode)
  • 23行:调用bufio的Writer结构体的Flush()将数据写入磁盘中
  • 27行:通过os打开或者操作的文件需要关闭

运行之后在项目目录下生成test.png图片,打开图片可以看到背景为透明,像素为300*300,如图:

2、下面例子表示绘制一个两个圆形,并设置两个圆之间边框颜色为红色,如图:

package main

import (
	"fmt"
	"image"
	"image/color"
	"image/draw"
	"image/png"
	"math"
	"os"
)

func main() {
	//backgroundColor := color.RGBA{0, 0, 0, 0}        //透明
	img := image.NewRGBA(image.Rect(0, 0, 600, 600)) //定义图像对象
	//在图像对象img上绘制新透明色
	//draw.Draw(img, img.Bounds(), image.NewUniform(backgroundColor), image.Point{}, draw.Src)
	//圆的中心坐标,因为我们希望画的圆形可以位于图像对象的中心,因为设置为300,可根据需要自定义
	centerX := 300
	centerY := 300
	//设置外层圆的半径,如果希望外层圆与图像对象相接,可设置半径为300
	radius := 300
	//设置内层圆与外层圆之间的边框宽度
	borderWidth := 10
	//定义外层圆的颜色,本例子设置为红色
	borderColor := color.RGBA{255, 0, 0, 255}
	//定义外层圆   2*math.Pi相当于圆的一周,遍历圆的一周上的点与水平方向的角度
	for angle := 0.0; angle < 2*math.Pi; angle += 0.001 {
		x := int(float64(radius)*math.Cos(angle)) + centerX
		y := int(float64(radius)*math.Sin(angle)) + centerY
		img.Set(x, y, borderColor)
	}
	//定义内层圆
	for angle := 0.0; angle < 2*math.Pi; angle += 0.001 {
		x := int(float64(radius-borderWidth)*math.Cos(angle)) + centerX
		y := int(float64(radius-borderWidth)*math.Sin(angle)) + centerY
		img.Set(x, y, borderColor)
	}
	//填充内圆与外侧圆之间的区域为红色
	for angle := 0.0; angle < 2*math.Pi; angle += 0.001 {
		//右上四分之一圆
		for x := int(float64(radius-borderWidth)*math.Cos(angle)) + centerX; x <= int(float64(radius)*math.Cos(angle))+centerX; x++ {
			for y := int(float64(radius)*math.Sin(angle)) + centerY; y <= int(float64(radius-borderWidth)*math.Sin(angle))+centerY; y++ {
				img.Set(x, y, borderColor)
			}
		}
		//左上四分之一圆
		for x := int(float64(radius)*math.Cos(angle)) + centerX; x <= int(float64(radius-borderWidth)*math.Cos(angle))+centerX; x++ {
			for y := int(float64(radius)*math.Sin(angle)) + centerY; y <= int(float64(radius-borderWidth)*math.Sin(angle))+centerY; y++ {
				img.Set(x, y, borderColor)
			}
		}
		//左下四分之一圆
		for x := int(float64(radius)*math.Cos(angle)) + centerX; x <= int(float64(radius-borderWidth)*math.Cos(angle))+centerX; x++ {
			for y := int(float64(radius-borderWidth)*math.Sin(angle)) + centerY; y <= int(float64(radius)*math.Sin(angle))+centerY; y++ {
				img.Set(x, y, borderColor)
			}
		}
		//右下四分之一圆
		for x := int(float64(radius-borderWidth)*math.Cos(angle)) + centerX; x <= int(float64(radius)*math.Cos(angle))+centerX; x++ {
			for y := int(float64(radius-borderWidth)*math.Sin(angle)) + centerY; y <= int(float64(radius)*math.Sin(angle))+centerY; y++ {
				img.Set(x, y, borderColor)
			}
		}
	}
	// 将图片输出到文件
	outputFile, err := os.Create("circle.png")
	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}
	defer outputFile.Close()
	png.Encode(outputFile, img)
}

说明:

上面的外层圆的 for 循环,遍历从 0 到 2π(即一周)之间的弧度值,并且每次递增0.001,具体来说,radius 表示点到圆心的距离,也就是我们设置的外层圆的半径,angle 表示圆上的点与圆心连线后与X轴的夹角(弧度),centerX 表示圆心在水平方向上的坐标。这段代码先将 radius 转换成浮点数类型并乘以 math.Cos(angle) 计算出点在水平方向上的距离(即 x 坐标),此坐标是相对于圆心的坐标,因此需要加上 centerX才能得到点在水平方向上的实际位置坐标,y坐标一样,最后通过调用 img.Set(x, y, borderColor),将该点的颜色设置为 borderColor,本例子中为红色,因此就画出了一个红色的圆形

内层圆的原理一样的,需要先用外层圆的半径减去内外层圆之间的边框距离,然后画圆即可,画好的图如下:

再来说一下如何给内圆和外圆之间的边框区域添加颜色为红色

原理:还是通过for循环遍历内外圆的各个点上的弧度,根据弧度值计算出每个点的X,Y坐标,然后通过image库的RGBA结构体的Set()方法设置坐标区间的颜色

首先我们看上面代码的第三个for循环,还是遍历一个圆周(2*math.Pi)上的点,获取这个点的弧度制,然后通过math.Cos()和math.Sin()来获取每个点对应的实际坐标,在设置颜色的时,其实是通过(600,300)这个点然后顺时针画的,也就是先画的右下四分之一圆,然后左下四分之一圆,以此类推,因此在for循环中又包含了四个for循环,说明如下:

右下四分之一圆:从遍历后x,y的坐标来看,右下四分之一圆的外层圆起始坐标为(600,300),内层圆的起始坐标为(590,300),外层圆终止坐标(300,600),内层圆终止坐标为(300,590),并且整个外层圆的x坐标始终大于等于内层圆的x坐标,外层圆的y坐标始终大于内层圆的y坐标,因此通过for循环从全部的结果中遍历符合的坐标为:

for x := int(float64(radius-borderWidth)*math.Cos(angle)) + centerX; x <= int(float64(radius)*math.Cos(angle))+centerX; x++ {
  for y := int(float64(radius-borderWidth)*math.Sin(angle)) + centerY; y <= int(float64(radius)*math.Sin(angle))+centerY; y++ {
		img.Set(x, y, borderColor)
  }
}

左下四分之一圆:左下四分之一圆的外层圆起始坐标为(300,600),内层圆为(300,590),外层圆终止坐标为(0,300),内层圆终止坐标为(10,300),因为边框厚度为10,因此内层圆的X坐标为10,从遍历的坐标结果看,外层圆的x坐标始终小于等于内层圆的坐标,外层圆的y坐标使用大于等于内层圆的Y坐标,因此通过for循环从全部的结果中遍历符合的坐标为:

for x := int(float64(radius)*math.Cos(angle)) + centerX; x <= int(float64(radius-borderWidth)*math.Cos(angle))+centerX; x++ {
	for y := int(float64(radius-borderWidth)*math.Sin(angle)) + centerY; y <= int(float64(radius)*math.Sin(angle))+centerY; y++ {
	img.Set(x, y, borderColor)
   }
}

左上四分之一圆:左上四分之一圆的外层圆起始坐标为(0,300),内层圆为(300,10),外层圆终止坐标为(300,0),内层圆为(300,10),从遍历的坐标结果看,外层圆的x坐标始终小于等于内层圆的x坐标,外层圆的y坐标始终小于等于内层圆的y坐标,因此通过for循环从全部的结果中遍历符合的坐标为:

for x := int(float64(radius)*math.Cos(angle)) + centerX; x <= int(float64(radius-borderWidth)*math.Cos(angle))+centerX; x++ {
	for y := int(float64(radius)*math.Sin(angle)) + centerY; y <= int(float64(radius-borderWidth)*math.Sin(angle))+centerY; y++ {
	img.Set(x, y, borderColor)
   }
}

右上四分之一圆:右上四分之一圆的外层圆起始坐标为(300,0),内层圆的起始坐标为(300,10),外层圆的终止坐标为(600,300),内层圆的终止坐标为(590,300),从遍历的x,y坐标结果看,外层圆的x坐标始终大于等于内层圆的x坐标,外层圆的Y坐标始终小于等于内层圆的Y坐标,因此通过for循环从全部的结果中遍历符合的坐标为:

for x := int(float64(radius-borderWidth)*math.Cos(angle)) + centerX; x <= int(float64(radius)*math.Cos(angle))+centerX; x++ {
	for y := int(float64(radius)*math.Sin(angle)) + centerY; y <= int(float64(radius-borderWidth)*math.Sin(angle))+centerY; y++ {
	img.Set(x, y, borderColor)
	}
}

注意:如果只是画一个内外圆并设置边框宽度为红色,第1、2个的for循环2*math.Pi其实可以不写的,也可以达到效果,但是如果只是画两个空心圆,那么第一个第二个就必须要写

其他:上面我们计算点的坐标是根据的弧度值,通过for循环遍历2*math.Pi获取的直接就是弧度值,如果给定角度,来获取坐标值,可以先将角度值转换为弧度值,然后在计算坐标,如下:

angle := 45                     //给定角度
radian := angle *math.Pi /180   //根据角度求弧度值
res := int(float64(radius)*math.Cos(radian)) + centerX  //根据弧度获取X坐标

标签