并行 Golang 中的 RGBA 到灰度

RGBA to Grayscale in parallel Golang

我编写了一个程序,可以将 RGBA 图像顺序转换为灰度图像。我现在正在尝试将其转换为并行运行。

我有点明白我需要怎么做,但我很难开始。

这是我目前的情况。

   package main

import (
"image"
"image/color"
"image/jpeg"
"log"
"os"
)

var lum float64


type ImageSet interface {
Set(x, y int, c color.Color)
}

func rgbtogray(r uint32, g uint32, b uint32)  float64{
    lum = 0.299*float64(r) + 0.587*float64(g) + 0.114*float64(b)
    return lum
}


func main() {
file, err := os.Open("flower.jpg")
if err != nil {
    log.Fatal(err)
}
defer file.Close()

img, err := jpeg.Decode(file)
if err != nil {
    log.Fatal(os.Stderr, "%s: %v\n", "flower.jpg", err)
}

channel1 := make(chan float64)
channel2 := make(chan float64)


b := img.Bounds()
imgSet := image.NewRGBA(b)

halfImage := b.Max.X/2
fullImage := b.Max.X

for y := 0; y < b.Max.Y; y++ {
      go func() {
        for x := 0; x < halfImage; x++ {
          oldPixel := img.At(x, y)
          r, g, b, _ := oldPixel.RGBA()
        channel1 <- rgbtogray(r, g, b)
        pixel := color.Gray{uint8(lum / 256)}
        imgSet.Set(x, y, pixel)
      }
      }()

      go func() {
        for x := halfImage; x< fullImage; x++ {
          oldPixel := img.At(x, y)
          r, g, b, _ := oldPixel.RGBA()
        channel2 <- rgbtogray(r, g, b)
        pixel := color.Gray{uint8(lum / 256)}
        imgSet.Set(x, y, pixel)
      }
      }()


}

    outFile, err := os.Create("changed.jpg")
    if err != nil {
      log.Fatal(err)
    }
    defer outFile.Close()
    jpeg.Encode(outFile, imgSet, nil)

}

这只运行 returns 黑色图像。我知道我处理它的方式是错误的,但我不是 100% 我需要走的路。

我的想法是将图像从中间分开,一个通道处理左侧的像素,一个通道处理右侧的像素。在向下移动到下一个 y 坐标之前等等。

我已经尝试将我的 go 函数中的所有代码移动到我的 rgbatogray 函数中,但是我遇到了多个与传递变量等有关的错误。是否最好创建另一个处理拆分等的函数因为我认为我调用我的 go 函数应该看起来像:

go func() {
      channel1 <- rgbtogray(r, g, b)
}()
go func() {
      channel2 <- rgbtogray(r, g, b)
}()

我不确定下一步应该采取什么步骤,所以非常感谢任何提示和帮助。

如何创建一个从图像中读取像素、转换为灰色并最终设置为新图像的管道,其中所有步骤 运行 并发并且每个步骤都可以在内部并行化?

然后 Go 的 运行time 将使用所有可用内核并行化任务。

此实现有效:

package main

import (
    "image"
    "image/color"
    "image/jpeg"
    "log"
    "os"
    "sync"
)

type Setter interface {
    Set(x, y int, c color.Color)
}

type Pixel struct {
    X, Y int
    C    color.Color
}

func sendPixels(in image.Image, out chan Pixel) {
    b := in.Bounds()
    for x := 0; x < b.Max.X; x++ {
        for y := 0; y < b.Max.Y; y++ {
            out <- Pixel{x, y, in.At(x, y)}
        }
    }
    close(out)
}

func convertToGray(in chan Pixel, out chan Pixel) {
    var wg sync.WaitGroup
    for p := range in {
        wg.Add(1)
        go func(p Pixel) {
            r, g, b, _ := p.C.RGBA()
            l := 0.299*float64(r) + 0.587*float64(g) + 0.114*float64(b)
            out <- Pixel{p.X, p.Y, color.Gray{uint8(l / 256)}}
            wg.Done()
        }(p)
    }
    wg.Wait()
    close(out)
}

func buildImage(in chan Pixel, out Setter, done chan int) {
    for p := range in {
        out.Set(p.X, p.Y, p.C)
    }
    close(done)
}

func main() {
    i, err := jpeg.Decode(os.Stdin)
    if err != nil {
        log.Fatalf("decoding image: %v", err)
    }

    a := make(chan Pixel, 1000)
    go sendPixels(i, a)

    b := make(chan Pixel, 1000)
    go convertToGray(a, b)

    c := image.NewRGBA(i.Bounds())
    d := make(chan int)
    go buildImage(b, c, d)

    <-d

    err = jpeg.Encode(os.Stdout, c, nil)
    if err != nil {
        log.Fatalf("encoding image: %v", err)
    }
}

哪个可以测试运行ning:

go run main.go <in.jpg >out.jpg

这是@JimB 在评论中的建议的实现。它利用 JPEG 图像在 YCbCr 中这一事实来处理图像设置,就地 Cb 和 Cr 组件到 128,每个组件使用一个 goroutine。

func set(wg *sync.WaitGroup, a []uint8, v uint8) {
    for i := 0; i < len(a); i++ {
        a[i] = v
    }
    wg.Done()
}

func gray(i *image.YCbCr) {
    var wg sync.WaitGroup
    wg.Add(2)
    go set(&wg, i.Cb, 128)
    go set(&wg, i.Cr, 128)
    wg.Wait()
}

func main() {
    i, err := jpeg.Decode(os.Stdin)
    if err != nil {
        log.Fatalf("decoding image: %v", err)
    }
    gray(i.(*image.YCbCr))
    err = jpeg.Encode(os.Stdout, i, nil)
    if err != nil {
        log.Fatalf("encoding image: %v", err)
    }
}

原来很简单。

当然可以扩展它以创建更多 goroutine(可能每个可用核心一个),为每个 goroutine 分配 Cb 和 Cr 数组的切片进行处理。