Szukaj…
Wprowadzenie
Pakiet obrazu zapewnia podstawowe funkcje do pracy z obrazem 2D. W tym temacie opisano kilka podstawowych operacji podczas pracy z obrazem, takich jak czytanie i zapisywanie określonego formatu obrazu, kadrowanie, uzyskiwanie dostępu i modyfikowanie pikseli , konwersja kolorów, zmiana rozmiaru i podstawowe filtrowanie obrazu.
Podstawowe koncepcje
Obraz reprezentuje prostokątną siatkę elementów obrazu ( piksel ). W pakiecie obrazu piksel jest reprezentowany jako jeden z kolorów zdefiniowanych w pakiecie obraz / kolor . Geometria 2D obrazu jest reprezentowana jako image.Rectangle
, natomiast image.Point
oznacza pozycję na siatce.
Powyższy rysunek ilustruje podstawowe koncepcje obrazu w pakiecie. Obraz o wymiarach 15 x 14 pikseli ma prostokątne granice rozpoczynające się w lewym górnym rogu (np. Współrzędna (-3, -4) na powyższym rysunku), a jego osie rosną w prawo i w dół do prawego dolnego rogu (np. Współrzędna ( 12, 10) na rysunku). Zauważ, że granice niekoniecznie zaczynają się od lub zawierają punkt (0,0) .
Typ związany z obrazem
W Go
obraz zawsze implementuje następujący interfejs 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
}
w którym interfejs color.Color
jest zdefiniowany jako
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)
}
i color.Model
to interfejs zadeklarowany jako
type Model interface {
Convert(c Color) Color
}
Dostęp do wymiaru obrazu i piksela
Załóżmy, że mamy obraz zapisany jako zmienna img
, a następnie możemy uzyskać wymiar i piksel obrazu poprzez:
// 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
}
}
Zauważ, że w pakiecie wartość każdego komponentu R,G,B,A
zawiera się między 0-65535
( 0x0000 - 0xffff
), a nie 0-255
.
Ładowanie i zapisywanie obrazu
W pamięci obraz może być postrzegany jako matryca pikseli (kolorów). Jednak gdy obraz jest przechowywany w pamięci trwałej, może być przechowywany w stanie takim, w jakim jest (format RAW), bitmapą lub innymi formatami obrazu ze szczególnym algorytmem kompresji w celu zaoszczędzenia miejsca, np. PNG, JPEG, GIF itp. Podczas ładowania obrazu z określonym formacie, obraz musi być dekodowane do image.Image
z odpowiednim algorytmem. Funkcja image.Decode
zadeklarowana jako
func Decode(r io.Reader) (Image, string, error)
jest przewidziany dla tego konkretnego zastosowania. Aby móc obsługiwać różne formaty obrazów, przed wywołaniem funkcji image.Decode
, dekoder musi zostać zarejestrowany poprzez funkcję image.RegisterFormat
zdefiniowaną jako
func RegisterFormat(name, magic string,
decode func(io.Reader) (Image, error), decodeConfig func(io.Reader) (Config, error))
Obecnie pakiet obrazów obsługuje trzy formaty plików: JPEG , GIF i PNG . Aby zarejestrować dekoder, dodaj następujące elementy
import _ "image/jpeg" //register JPEG decoder
do main
pakietu aplikacji. Gdzieś w kodzie (niepotrzebne w pakiecie main
), aby załadować obraz JPEG, użyj następujących fragmentów:
f, err := os.Open("inputimage.jpg")
if err != nil {
// Handle error
}
defer f.Close()
img, fmtName, err := image.Decode(f)
if err != nil {
// Handle error
}
// `fmtName` contains the name used during format registration
// Work with `img` ...
Zapisz w PNG
Aby zapisać obraz w określonym formacie, odpowiedni koder musi zostać zaimportowany jawnie, tj
import "image/png" //needed to use `png` encoder
wtedy obraz można zapisać za pomocą następujących fragmentów:
f, err := os.Create("outimage.png")
if err != nil {
// Handle error
}
defer f.Close()
// Encode to `PNG` with `DefaultCompression` level
// then save to file
err = png.Encode(f, img)
if err != nil {
// Handle error
}
Jeśli chcesz określić poziom kompresji inny niż DefaultCompression
, utwórz koder , np
enc := png.Encoder{
CompressionLevel: png.BestSpeed,
}
err := enc.Encode(f, img)
Zapisz w JPEG
Aby zapisać w formacie jpeg
, użyj następujących poleceń:
import "image/jpeg"
// Somewhere in the same package
f, err := os.Create("outimage.jpg")
if err != nil {
// Handle error
}
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 {
// Handle error
}
Zapisz w formacie GIF
Aby zapisać obraz w pliku GIF, użyj następujących fragmentów.
import "image/gif"
// Samewhere in the same package
f, err := os.Create("outimage.gif")
if err != nil {
// Handle error
}
defer f.Close()
opt := gif.Options {
NumColors: 256,
// Add more parameters as needed
}
err = gif.Encode(f, img, &opt)
if err != nil {
// Handle error
}
Kadrowanie obrazu
Większość typów obrazów w pakiecie obrazów ma SubImage(r Rectangle) Image
, z wyjątkiem image.Uniform
. W oparciu o ten fakt możemy zaimplementować funkcję przycinania dowolnego obrazu w następujący sposób
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
}
Zwróć uwagę, że przycięty obraz może współdzielić podstawowe piksele z obrazem oryginalnym. W takim przypadku jakakolwiek modyfikacja przyciętego obrazu wpłynie na oryginalny obraz.
Konwertuj kolorowy obraz na skalę szarości
Niektóre algorytmy cyfrowego przetwarzania obrazu, takie jak wykrywanie krawędzi, informacje przenoszone przez intensywność obrazu (tj. Wartość skali szarości) są wystarczające. Korzystanie z informacji o kolorze (kanał R, G, B
) może zapewnić nieco lepszy wynik, ale złożoność algorytmu zostanie zwiększona. Dlatego w tym przypadku przed zastosowaniem takiego algorytmu musimy przekonwertować obraz kolorowy na obraz w skali szarości.
Poniższy kod jest przykładem konwersji dowolnego obrazu na 8-bitowy obraz w skali szarości. Obraz jest pobierany ze zdalnej lokalizacji za pomocą pakietu net/http
, konwertowany na skalę szarości i ostatecznie zapisywany jako obraz 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 {
// handle error
log.Fatal(err)
}
defer resp.Body.Close()
// Decode image to JPEG
img, _, err := image.Decode(resp.Body)
if err != nil {
// handle error
log.Fatal(err)
}
log.Printf("Image type: %T", img)
// Converting 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 {
// handle error
log.Fatal(err)
}
defer f.Close()
if err := png.Encode(f, grayImg); err != nil {
log.Fatal(err)
}
}
Konwersja kolorów następuje podczas przypisywania pikseli przez Set(x, y int, c color.Color)
który jest zaimplementowany w image.go
as
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
}
w którym color.GrayModel
jest zdefiniowany w color.go
as
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)}
}
Na podstawie powyższych faktów intensywność Y
oblicza się według następującego wzoru:
Luminancja: Y = 0,299 R + 0,587 G + 0,114 B
Jeśli chcemy zastosować inną formułę / algorytm do konwersji koloru na intensywność, np
Średnia: Y = ( R + G + B ) / 3
Luma: Y = 0,2126 R + 0,7152 G + 0,0722 B.
Połysk: Y = (min ( R , G , B ) + max ( R , G , B )) / 2
następnie można użyć następujących fragmentów.
// Converting 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)
}
}
Powyższe obliczenia są wykonywane przez mnożenie zmiennoprzecinkowe iz pewnością nie jest najbardziej wydajne, ale wystarcza do zademonstrowania pomysłu. Drugim punktem jest, gdy wywołując Set(x, y int, c color.Color)
z color.Gray
jako trzeci argument, model kolorów nie wykona konwersji kolorów, jak widać w poprzedniej funkcji grayModel
.