在并行的Golang中将RGBA转换为灰度图像

huangapple go评论76阅读模式
英文:

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)
}

这段代码可以运行,但只返回一张黑色的图像。我知道我处理的方式是错误的,但我不确定应该采取哪种方法。

我的想法是将图像分成两半,一个通道处理左边的像素,另一个通道处理右边的像素。然后再移动到下一个y坐标,以此类推。

我尝试将go函数中的所有代码移动到rgbtogray函数中,但是出现了多个与传递变量等有关的错误。是否最好创建另一个函数来处理分割等操作?我认为调用go函数应该像这样:

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

我不确定接下来应该采取哪些步骤,所以非常感谢任何提示和帮助。

英文:

I have written a program which converts RGBA image to Grayscale sequentially. I'm now trying to convert it so it runs in parallel.

I kind of understand how I need to be doing this but I'm struggling to get started.

Here is what I have so far.

   package main
import (
&quot;image&quot;
&quot;image/color&quot;
&quot;image/jpeg&quot;
&quot;log&quot;
&quot;os&quot;
)
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(&quot;flower.jpg&quot;)
if err != nil {
log.Fatal(err)
}
defer file.Close()
img, err := jpeg.Decode(file)
if err != nil {
log.Fatal(os.Stderr, &quot;%s: %v\n&quot;, &quot;flower.jpg&quot;, 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 &lt; b.Max.Y; y++ {
go func() {
for x := 0; x &lt; halfImage; x++ {
oldPixel := img.At(x, y)
r, g, b, _ := oldPixel.RGBA()
channel1 &lt;- rgbtogray(r, g, b)
pixel := color.Gray{uint8(lum / 256)}
imgSet.Set(x, y, pixel)
}
}()
go func() {
for x := halfImage; x&lt; fullImage; x++ {
oldPixel := img.At(x, y)
r, g, b, _ := oldPixel.RGBA()
channel2 &lt;- rgbtogray(r, g, b)
pixel := color.Gray{uint8(lum / 256)}
imgSet.Set(x, y, pixel)
}
}()
}
outFile, err := os.Create(&quot;changed.jpg&quot;)
if err != nil {
log.Fatal(err)
}
defer outFile.Close()
jpeg.Encode(outFile, imgSet, nil)
}

This runs but just returns a black image. I know the way I'm going about it is wrong but I'm not 100% what route I need to be taking.

My idea is to split the image down the middle, have one channel work on the pixels on the left and one channel work on the pixels on the right. Before moving down to the next y coordinate and so on.

I've tried moving all of the code in my go functions into my rgbatogray function but I was getting multiple errors to do with passing through variables etc. Would it be best to create another function which deals with the splitting etc as I think I calling my go functions should just look something like:

go func() {
channel1 &lt;- rgbtogray(r, g, b)
}()
go func() {
channel2 &lt;- rgbtogray(r, g, b)
}()

I'm unsure what steps I should be taking next on this so any tips and help greatly appreciated.

答案1

得分: 2

这是@JimB在评论中提出的建议的实现方式。它利用了JPEG图像采用YCbCr格式的特点,使用一个goroutine来处理图像,将Cb和Cr分量设置为128。

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("解码图像:%v", err)
    }
    gray(i.(*image.YCbCr))
    err = jpeg.Encode(os.Stdout, i, nil)
    if err != nil {
        log.Fatalf("编码图像:%v", err)
    }
}

这个实现非常简单。

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

英文:

Here's an implementation of @JimB's suggestion in comments. It exploits the fact of JPEG images being in YCbCr to process the image setting inplace Cb and Cr components to 128 using one goroutine for each of them.

func set(wg *sync.WaitGroup, a []uint8, v uint8) {
for i := 0; i &lt; len(a); i++ {
a[i] = v
}
wg.Done()
}
func gray(i *image.YCbCr) {
var wg sync.WaitGroup
wg.Add(2)
go set(&amp;wg, i.Cb, 128)
go set(&amp;wg, i.Cr, 128)
wg.Wait()
}
func main() {
i, err := jpeg.Decode(os.Stdin)
if err != nil {
log.Fatalf(&quot;decoding image: %v&quot;, err)
}
gray(i.(*image.YCbCr))
err = jpeg.Encode(os.Stdout, i, nil)
if err != nil {
log.Fatalf(&quot;encoding image: %v&quot;, err)
}
}

It turned out pretty simple.

Of course it could be extended to create more goroutines (possibly one per available core) assigning slices of Cb & Cr arrays to each for processing.

答案2

得分: 0

创建一个从图像中读取像素、转换为灰度并设置到新图像的流水线,其中所有步骤并行运行,每个步骤都可以内部并行化。然后,Go的运行时将使用所有可用的核心并行化任务。

以下是这个实现的代码:

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)
	}
}

可以通过以下方式进行测试:

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

这段代码实现了一个并行处理图像像素的流水线,包括像素读取、灰度转换和图像构建。

英文:

How about creating a pipeline of pixels read from the image, converted to gray and finally set to a new image, where all steps run concurrently and each step could be internally parallelized?

Then Go's runtime will parallelize the tasks using all available cores.

This implementation works:

package main
import (
&quot;image&quot;
&quot;image/color&quot;
&quot;image/jpeg&quot;
&quot;log&quot;
&quot;os&quot;
&quot;sync&quot;
)
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 &lt; b.Max.X; x++ {
for y := 0; y &lt; b.Max.Y; y++ {
out &lt;- 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 &lt;- 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(&quot;decoding image: %v&quot;, 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)
&lt;-d
err = jpeg.Encode(os.Stdout, c, nil)
if err != nil {
log.Fatalf(&quot;encoding image: %v&quot;, err)
}
}

Which can be tested running:

go run main.go &lt;in.jpg &gt;out.jpg

huangapple
  • 本文由 发表于 2017年3月3日 02:31:22
  • 转载请务必保留本文链接:https://go.coder-hub.com/42563199.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定