将RGBA图像转换为灰度Golang

将RGBA图像转换为灰度Golang,go,colors,rgb,image-manipulation,grayscale,Go,Colors,Rgb,Image Manipulation,Grayscale,我目前正在开发一个程序,将RGBA图像转换为灰度 我早些时候问了一个问题,并被引导到下面的答案- 这是我原来的问题- 我已经编辑了我的代码,所以它现在可以成功运行了——但是输出的图像不是我想要的。它被转换成灰度,但是像素都被弄乱了,看起来像旧电视上的噪音 package main import ( "image" "image/color" "image/jpeg" "log" "os" ) type ImageSet interface { Set(x, y int, c color.C

我目前正在开发一个程序,将RGBA图像转换为灰度

我早些时候问了一个问题,并被引导到下面的答案-

这是我原来的问题-

我已经编辑了我的代码,所以它现在可以成功运行了——但是输出的图像不是我想要的。它被转换成灰度,但是像素都被弄乱了,看起来像旧电视上的噪音

package main

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

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


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

b := img.Bounds()

imgSet := image.NewRGBA(b)
for y := 0; y < b.Max.Y; y++ {
    for x := 0; x < b.Max.X; x++ {
      oldPixel := img.At(x, y)
       r, g, b, a:= oldPixel.RGBA()
       r = (r+g+b)/3
       pixel := color.RGBA{uint8(r), uint8(r), uint8(r), uint8(a)}
       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)

}
我目前得到以下错误

.\rgbtogray.go:36: cannot use y (type uint32) as type int in argument to imgSet.Set
我在回答中遗漏了什么吗?任何提示都值得欣赏。

是一种返回alpha预乘红色、绿色、蓝色和alpha值的方法,所有值均为
uint32
,但仅在
[0,0xffff]
范围内(仅使用32位中的16位)。这意味着您可以添加这些组件,它们不会溢出(每个组件的最大值适合16位,所以它们的总和适合32位)

这里需要注意的一点是:结果也将是alpha预乘,除以3后,它仍将在
[0..0xffff]
的范围内。因此,通过执行
uint8(r)
类型转换,您只需保留最低的8位,与整数相比,这似乎只是一个随机值。你应该取最高的8位

但没那么快。我们在这里要做的是将彩色图像转换为灰度图像,这将丢失“颜色”信息,我们需要的基本上是每个像素的亮度。您提出的解决方案称为“平均法”,其结果相当糟糕,因为它将所有R、G和B分量的权重相等,即使这些颜色具有不同的波长,从而对整个像素的亮度产生不同的影响。请在此处阅读更多信息:

对于真实的RGB->灰度转换,必须使用以下权重:

Y = 0.299 * R +  0.587 * G + 0.114 * B
您可以在维基百科上阅读这些权重(和变量)背后的更多信息:。这就是所谓的光度法,这将提供最好的灰度图像

到目前为止还不错,我们有光度,我们如何从这里得到一个
color.color
值?一个选项是使用颜色值,其中为所有组件指定相同的亮度(可以保留alpha)。如果您打算使用返回的,这可能是最好的方法,因为在设置颜色时不需要进行颜色转换(因为它与图像的颜色模型匹配)

另一个诱人的选择是使用,它是一种颜色(实现
color.color
接口),并按照我们现在的方式对颜色建模:使用
Y
,使用
uint8
存储。另一种方法是基本上“相同”,但使用16位将
Y
存储为
uint16
。对于这些,最好也使用具有匹配颜色模型的图像,例如or(尽管这不是要求)

因此,转换应为:

oldPixel := img.At(x, y)
r, g, b, _ := oldPixel.RGBA()
lum := 0.299*float64(r) + 0.587*float64(g) + 0.114*float64(b)
pixel := color.Gray{uint8(lum / 256)}
imgSet.Set(x, y, pixel)
请注意,我们需要将R、G、B分量转换为
float64
,以便能够乘以权重。由于
r
g
b
已经属于
uint32
类型,我们可以用整数运算(无溢出)来代替它

不必详细说明,因为标准库已经有了解决方案,这里是:

oldPixel := img.At(x, y)
r, g, b, _ := oldPixel.RGBA()
lum := (19595*r + 38470*g + 7471*b + 1<<15) >> 24
imgSet.Set(x, y, color.Gray{uint8(lum)})
它与上一个亮度加权模型相同,使用整数算法。或者在一行中:

imgSet.Set(x, y, color.GrayModel.Convert(img.At(x, y)))
要获得更高的16位灰度分辨率,请执行以下操作:

imgSet.Set(x, y, color.Gray16Model.Convert(img.At(x, y)))

最后一点注意:由于您是在
image.NewRGBA()
返回的图像上绘制的,因此它的类型为
*image.RGBA
。您不需要检查它是否有一个
Set()
方法,因为
image.RGBA
是一个静态类型(不是接口),并且它确实有一个
Set()
方法,它是在编译时检查的。您确实需要检查的情况是,您是否有一个作为接口的通用类型的映像,但此接口不包含/“规定”
Set()
方法;但是实现此接口的动态类型可能会提供这一点。

答案不错!我不想进入亮度,但我可能应该包括
GrayModel
。嗨,我没有意识到有更好的方法来转换为灰度。我已将您的代码放入我的代码中,它返回一个关于y的错误。错误如下所示,.\rgbtogray.go:36:无法在imgSet.Set的参数中使用y(类型uint32)作为int类型。还有什么提示吗?@benjano你确定你正确复制了我的代码吗?听起来你错过了传递
x
y
imgSet.Set()
。如果没有,请准确地说明是什么导致了错误。我很确定,我已经编辑了我的答案,以显示我的代码处于当前状态。问题可能是变量名为y,它是坐标变量吗?我已经将其更改为Z,现在代码运行,但它只返回一个空白图像
imgSet.Set(x, y, color.GrayModel.Convert(img.At(x, y)))
imgSet.Set(x, y, color.Gray16Model.Convert(img.At(x, y)))