将 RGBA 图像转换为灰度 Golang

Converting RGBA image to Grayscale Golang

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

我之前问过一个问题,并被定向到以下答案 -

这是我原来的问题 - Program to convert RGBA to grayscale Golang

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

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)

}

我知道我没有在 if else 语句中添加检查图像是否可以接受 Set() 方法,但是简单地制作一个新图像的建议似乎已经解决了这个问题。

非常感谢任何帮助。

编辑:

我从下面的答案中添加了一些建议的代码:

    package main

import (
//"fmt"
"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, _ := oldPixel.RGBA()
      y := 0.299*float64(r) + 0.587*float64(g) + 0.114*float64(b)
      pixel := color.Gray{uint8(y / 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)

}

我目前收到以下错误。

.\rgbtogray.go:36: cannot use y (type uint32) as type int in argument to imgSet.Set

我是否遗漏了答案?任何提示表示赞赏。

Color.RGBA()是returnsalpha预乘红、绿、蓝、alpha值的方法,都是uint32类型,但只在[范围内=18=](仅使用 32 位中的 16 位)。这意味着您可以添加这些组件,它们不会溢出(每个组件的最大值适合 16 位,因此它们的总和将适合 32 位)。

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

但没那么快。我们这里要做的是将彩色图像转换为灰度图像,这会丢失 "color" 信息,而我们想要的基本上是每个像素的亮度。您提出的解决方案称为 average 方法,它给出的结果相当差,因为它采用相同权重的所有 R、G 和 B 分量,即使这些颜色具有不同的波长,因此以不同的方式对整个像素的亮度做出贡献。在这里阅读更多相关信息:Grayscale to RGB Conversion.

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

Y = 0.299 * R +  0.587 * G + 0.114 * B

您可以在维基百科上阅读这些权重(和变体)背后的更多信息:Grayscale。这称为 亮度 方法,这将提供最佳的灰度图像。

到目前为止一切顺利,我们有了亮度,我们如何从这里获得 color.Color 值?一种选择是使用 color.RGBA color value, where you specify the same luminosity to all components (alpha may be kept). And if you intend to use an image.RGBA returned by image.NewRGBA(),这可能是最好的方法,因为设置颜色时不需要颜色转换(因为它与图像的颜色模型相匹配)。

另一个诱人的选择是使用 color.Gray which is a color (implements the color.Color interface), and models a color just the way we have it now: with Y, stored using an uint8. An alternative could be color.Gray16 which is basically the "same", but uses 16 bits to store Y as an uint16. For these, best would be to also use an image with the matching color model, such as image.Gray or image.Gray16(虽然这不是必需的)。

所以转换应该是:

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 才能乘以权重。由于 rgb 已经是 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)})

现在不用写"ugly"这种东西了,推荐的方法就是直接用image/color package, called Model的颜色转换器。准备好的 color.GrayModel 模型可以将任何颜色转换为 color.Gray.

的模型

就这么简单:

oldPixel := img.At(x, y)
pixel := color.GrayModel.Convert(oldPixel)
imgSet.Set(x, y, pixel)

它与我们上一个 光度 加权模型相同,使用整数算术。或者在一行中:

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() 方法,它在编译时检查时间。确实需要检查的情况是,如果你有一个通用image.Image类型的图像是一个接口,但是这个接口不包含/"prescribe" Set()方法;但是实现此接口的动态类型可能仍然提供该接口。