图像标准库为处理图像提供了基础。
image包提供了:
image.Image接口描述位图图像
一种最常见的表示内存中图像的方式的实现,例如Image.RGBA
向image.RegisterFormat注册图像格式解码器(例如PNG,JPEG等)的方法。
标准库提供了:
- GIF编码和解码
- PNG编码和解码
- JPEG编码和解码
其他库还有:
- BMP 编码和解码
- TIFF 编码和解码
- WEBP 解码
- vp8 解码
- vp8l 解码.
image.Image接口是最小的:
type Image interface {
// ColorModel returns the Image's color model.
ColorModel() color.Model
// Bounds returns the domain for which At can return non-zero color.
// The bounds do not necessarily contain the point (0, 0).
Bounds() Rectangle
// At returns the color of the pixel at (x, y).
// At(Bounds().Min.X, Bounds().Min.Y) returns the upper-left pixel of the grid.
// At(Bounds().Max.X-1, Bounds().Max.Y-1) returns the lower-right one.
At(x, y int) color.Color
}
这是从URL解码PNG图像并使用Bounds()方法打印图像大小的示例:
func showImageSize(uri string) {
resp, err := http.Get(uri)
if err != nil {
log.Fatalf("http.Get('%s') failed with %s\n", uri, err)
}
defer resp.Body.Close()
if resp.StatusCode >= 400 {
log.Fatalf("http.Get() failed with '%s'\n", resp.Status)
}
img, err := png.Decode(resp.Body)
if err != nil {
log.Fatalf("png.Decode() failed with '%s'\n", err)
}
size := img.Bounds().Size()
fmt.Printf("Image '%s'\n", uri)
fmt.Printf(" size: %dx%d\n", size.X, size.Y)
fmt.Printf(" format in memory: '%T'\n", img)
}
输出:
Image 'https://www.programming-books.io/covers/Go.png'
size: 595x842
format in memory: '*image.NRGBA'
基本概念
图像代表像素的矩形网格。 在image包中,像素表示为image/color包中定义的颜色之一。 图像的二维几何图形表示为image.Rectangle,image.Point表示网格上的位置。
图像和二维几何
[图片上传失败...(image-7c7e93-1635385188344)]
上图说明了包中图像的基本概念。尺寸为15x14像素的图像具有从左上角开始的矩形边界(例如,上图中的坐标(-3,-4)),并且其轴从右到下增加到右下角(例如,坐标( 图中的12、10)。 请注意,边界不一定始于或包含点(0,0)。
图像相关类型
在Go中,图像始终实现以下image.Image接口
type Image interface {
// ColorModel returns the Image's color model.
ColorModel() color.Model
// Bounds returns the domain for which At can return non-zero color.
// The bounds do not necessarily contain the point (0, 0).
Bounds() Rectangle
// At returns the color of the pixel at (x, y).
// At(Bounds().Min.X, Bounds().Min.Y) returns the upper-left pixel of the grid.
// At(Bounds().Max.X-1, Bounds().Max.Y-1) returns the lower-right one.
At(x, y int) color.Color
}
其中color.Color接口定义为:
type Color interface {
// RGBA returns the alpha-premultiplied red, green, blue and alpha values
// for the color. Each value ranges within [0, 0xffff], but is represented
// by a uint32 so that multiplying by a blend factor up to 0xffff will not
// overflow.
//
// An alpha-premultiplied color component c has been scaled by alpha (a),
// so has valid values 0 <= c <= a.
RGBA() (r, g, b, a uint32)
}
color.Model接口如下:
type Model interface {
Convert(c Color) Color
}
访问图像尺寸和像素
假设我们将图像存储为变量img,则可以通过以下方式获取尺寸和图像像素:
// Image bounds and dimension
b := img.Bounds()
width, height := b.Dx(), b.Dy()
// do something with dimension ...
// Corner co-ordinates
top := b.Min.Y
left := b.Min.X
bottom := b.Max.Y
right := b.Max.X
// Accessing pixel. The (x,y) position must be
// started from (left, top) position not (0,0)
for y := top; y < bottom; y++ {
for x := left; x < right; x++ {
cl := img.At(x, y)
r, g, b, a := cl.RGBA()
// do something with r,g,b,a color component
}
}
请注意,每个R,G,B,A组件的值都在0-65535(0x0000-0xffff)之间,而不是0-255。
加载和保存图像
在内存中,图像可以看作是像素(颜色)的矩阵。但是当将图像存储在永久存储器中时,可以按原始(RAW格式)位图或其他具有特定压缩算法的图像格式进行存储,以节省存储空间,例如 PNG,JPEG,GIF等。加载特定格式的图像时,必须使用相应的算法将图像解码为image.Image。
image.Decode函数声明为
func Decode(r io.Reader) (Image, string, error)
提供此特定用途。 为了能够处理各种图像格式,在调用image.Decode函数之前,必须通过定义为image.RegisterFormat函数的解码器进行注册。
func RegisterFormat(name, magic string,
decode func(io.Reader) (Image, error), decodeConfig func(io.Reader) (Config, error))
目前,image包支持三种文件格式:JPEG,GIF和PNG。 要注册解码器,请添加以下内容:
import _ "image/jpeg" //register JPEG decoder
到应用程序的主程序包。
如要在代码中的某个位置(在主程序包中不是必需的)加载JPEG图像,请使用以下代码段:
f, err := os.Open("inputimage.jpg")
if err != nil {
log.Fatalf("os.Open() failed with %s\n", err)
}
defer f.Close()
img, fmtName, err := image.Decode(f)
if err != nil {
log.Fatalf("image.Decode() failed with %s\n", err)
}
// `fmtName` contains the name used during format registration
// Work with `img` ...
保存png图片
为了将图像保存为特定格式,必须明确导入相应的编码器,即
import "image/png" //needed to use png encoder
然后可以使用以下代码段保存图像:
f, err := os.Create("outimage.png")
if err != nil {
log.Fatalf("os.Create() failed with %s\n", err)
}
defer f.Close()
// Encode to `PNG` with `DefaultCompression` level
// then save to file
err = png.Encode(f, img)
if err != nil {
log.Fatalf("png.Encode() failed with %s\n", err)
}
如果您要指定默认压缩级别以外的其他压缩级别,请创建一个编码器,例如:
enc := png.Encoder{
CompressionLevel: png.BestSpeed,
}
err := enc.Encode(f, img)
保存JPEG图片
要保存为jpeg格式,请使用以下命令:
import "image/jpeg"
// Somewhere in the same package
f, err := os.Create("outimage.jpg")
if err != nil {
log.Fatalf("os.Create() failed with %s\n", err)
}
defer f.Close()
// Specify the quality, between 0-100
// Higher is better
opt := jpeg.Options{
Quality: 90,
}
err = jpeg.Encode(f, img, &opt)
if err != nil {
log.Fatalf("jpeg.Encode() failed with %s\n", err)
}
保存GIF图片
要将图像保存到GIF文件,请使用以下代码段:
import "image/gif"
// Samewhere in the same package
f, err := os.Create("outimage.gif")
if err != nil {
log.Fatalf("os.Create() failed with %s\n", err)
}
defer f.Close()
opt := gif.Options {
NumColors: 256,
// Add more parameters as needed
}
err = gif.Encode(f, img, &opt)
if err != nil {
log.Fatalf("gif.Encode() failed with %s\n", err)
}
裁剪图像
除image.Uniform外,大多数具有SubImage(r Rectangle)Image方法的图像包中的图像类型。 基于这一事实,我们可以实现如下功能来裁剪任意图像
func CropImage(img image.Image, cropRect image.Rectangle) (cropImg image.Image, newImg bool) {
//Interface for asserting whether `img`
//implements SubImage or not.
//This can be defined globally.
type CropableImage interface {
image.Image
SubImage(r image.Rectangle) image.Image
}
if p, ok := img.(CropableImage); ok {
// Call SubImage. This should be fast,
// since SubImage (usually) shares underlying pixel.
cropImg = p.SubImage(cropRect)
} else if cropRect = cropRect.Intersect(img.Bounds()); !cropRect.Empty() {
// If `img` does not implement `SubImage`,
// copy (and silently convert) the image portion to RGBA image.
rgbaImg := image.NewRGBA(cropRect)
for y := cropRect.Min.Y; y < cropRect.Max.Y; y++ {
for x := cropRect.Min.X; x < cropRect.Max.X; x++ {
rgbaImg.Set(x, y, img.At(x, y))
}
}
cropImg = rgbaImg
newImg = true
} else {
// Return an empty RGBA image
cropImg = &image.RGBA{}
newImg = true
}
return cropImg, newImg
}
请注意,裁剪后的图像可能会与原始图像共享其基础像素。 在这种情况下,对裁剪图像的任何修改都会影响原始图像。
将彩色图像转换为灰度
一些数字图像处理算法,例如边缘检测,图像强度所携带的信息(即灰度值)就足够了。 使用颜色信息(R,G,B通道)可能会提供更好的结果,但是会增加算法的复杂性。 因此,在这种情况下,我们需要在应用这种算法之前将彩色图像转换为灰度图像。
以下代码是将任意图像转换为8位灰度图像的示例。 使用net/http软件包从远程位置检索图像,将其转换为灰度,最后保存为PNG图像:
package main
import (
"image"
"log"
"net/http"
"os"
_ "image/jpeg"
"image/png"
)
func main() {
// Load image from remote through http
// The Go gopher was designed by Renee French. (http://reneefrench.blogspot.com/)
// Images are available under the Creative Commons 3.0 Attributions license.
resp, err := http.Get("http://golang.org/doc/gopher/fiveyears.jpg")
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close()
// Decode image to JPEG
img, _, err := image.Decode(resp.Body)
if err != nil {
log.Fatal(err)
}
log.Printf("Image type: %T", img)
// Convert image to grayscale
grayImg := image.NewGray(img.Bounds())
for y := img.Bounds().Min.Y; y < img.Bounds().Max.Y; y++ {
for x := img.Bounds().Min.X; x < img.Bounds().Max.X; x++ {
grayImg.Set(x, y, img.At(x, y))
}
}
// Working with grayscale image, e.g. convert to png
f, err := os.Create("fiveyears_gray.png")
if err != nil {
log.Fatal(err)
}
defer f.Close()
if err := png.Encode(f, grayImg); err != nil {
log.Fatal(err)
}
}
通过Set(x,y int,c color.Color)分配像素时发生颜色转换,这在image.go中实现为
func (p *Gray) Set(x, y int, c color.Color) {
if !(Point{x, y}.In(p.Rect)) {
return
}
i := p.PixOffset(x, y)
p.Pix[i] = color.GrayModel.Convert(c).(color.Gray).Y
}
其中,color.GrayModel在color.go中定义为:
func grayModel(c Color) Color {
if _, ok := c.(Gray); ok {
return c
}
r, g, b, _ := c.RGBA()
// These coefficients (the fractions 0.299, 0.587 and 0.114) are the same
// as those given by the JFIF specification and used by func RGBToYCbCr in
// ycbcr.go.
//
// Note that 19595 + 38470 + 7471 equals 65536.
//
// The 24 is 16 + 8. The 16 is the same as used in RGBToYCbCr. The 8 is
// because the return value is 8 bit color, not 16 bit color.
y := (19595*r + 38470*g + 7471*b + 1<<15) >> 24
return Gray{uint8(y)}
}
基于上述事实,强度Y通过以下公式计算:
Luminance: Y = 0.299R + 0.587G + 0.114B
如果我们想应用不同的公式/算法将颜色转换为强度,例如:
Mean: Y = (R + G + B) / 3 Luma: Y = 0.2126R + 0.7152G + 0.0722B Luster: Y = (min(R, G, B) + max(R, G, B))/2
然后,可以使用以下片段。
// Convert image to grayscale
grayImg := image.NewGray(img.Bounds())
for y := img.Bounds().Min.Y; y < img.Bounds().Max.Y; y++ {
for x := img.Bounds().Min.X; x < img.Bounds().Max.X; x++ {
R, G, B, _ := img.At(x, y).RGBA()
//Luma: Y = 0.2126*R + 0.7152*G + 0.0722*B
Y := (0.2126*float64(R) + 0.7152*float64(G) + 0.0722*float64(B)) * (255.0 / 65535)
grayPix := color.Gray{uint8(Y)}
grayImg.Set(x, y, grayPix)
}
}
上面的计算是通过浮点乘法完成的,虽然肯定不是最有效的方法,但是足以证明这一点。 另一点是,当使用color.Gray作为第三个参数调用Set(x,y int,c color.Color)时,颜色模型将不会执行颜色转换,就像在以前的grayModel函数中可以看到的那样。
调整图像大小
有几个库可以在Go中调整图像大小。 软件包golang.org/x/image/draw是以下选项之一:
func resize(src image.Image, dstSize image.Point) *image.RGBA {
srcRect := src.Bounds()
dstRect := image.Rectangle{
Min: image.Point{0, 0},
Max: dstSize,
}
dst := image.NewRGBA(dstRect)
draw.CatmullRom.Scale(dst, dstRect, src, srcRect, draw.Over, nil)
return dst
}
要点:
我们返回image.RGBA而不是image.Image,因为Go的最佳实践是接受接口作为函数的参数,但返回具体类型
图像边界不必从(0,0)开始,因此对于源图像,我们要求边界矩形。我们自己创建目标图像,所以我们创建的边界以(0,0)开始
缩放接口非常通用,因此有些复杂。包绘制是其核心,它是一个合成引擎,它允许使用特定的操作来合成(绘制)位图图像。 draw.Over是一种操作,可以选择使用遮罩在一个图像上绘制另一个图像。
resizing是作为Scaler接口实现的,它接收目标图像,目标图像内的矩形,源图像,源图像内的矩形并将源矩形绘制为目标矩形。当目标矩形的尺寸与源矩形的尺寸不同时,会发生大小调整
该软件包实现了4种不同的缩放算法:
最近的邻居-快速,低质量
近似双线性-速度较慢,质量更好
双线性-甚至更慢,高质量
catmull-rom-最慢,最高质量
draw.CatmullRom是实现catmull-rom算法的实例的全局变量。其他算法可以作为draw.NearestNeighbor,draw.ApproxBiLinear和draw.BiLinear包全局变量使用
放在一起:要将源图像调整为给定大小,我们创建所需大小的目标图像,然后使用全局缩放器之一的Scale方法将源图像绘制到目标中,而调整大小是使用scaler的副作用。