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.

Obraz i geometria 2D

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 .



Modified text is an extract of the original Stack Overflow Documentation
Licencjonowany na podstawie CC BY-SA 3.0
Nie związany z Stack Overflow