将RGBA图像转换为Grayscale Golang

ben*_*ano 6 rgb image-manipulation colors go grayscale

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

我之前提出了一个问题并被引导到以下答案 - 更改单个像素的颜色 - Go lang图像

这是我原来的问题 - 将RGBA转换为灰度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)

}
Run Code Online (Sandbox Code Playgroud)

我知道我没有在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)

}
Run Code Online (Sandbox Code Playgroud)

我目前收到以下错误.

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

我错过了答案吗?任何提示赞赏.

icz*_*cza 22

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

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

但不是那么快.我们在这里要做的是将彩色图像转换为灰度图像,这将丢失"颜色"信息,我们想要的基本上是每个像素的亮度.你提出的解决方案被称为平均方法,它给出了相当差的结果,因为它使所有的R,G和B分量具有相同的权重,即使这些颜色具有不同的波长,因此对整体的亮度有不同的贡献.像素.在此处阅读更多相关信息:灰度到RGB转换.

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

Y = 0.299 * R +  0.587 * G + 0.114 * B
Run Code Online (Sandbox Code Playgroud)

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

到目前为止,我们有光明,我们如何color.Color从这里获得价值?一种选择是使用color.RGBA颜色值,您可以为所有组件指定相同的亮度(可以保留alpha).如果你打算使用image.RGBA返回的image.NewRGBA(),可能这是最好的方法,因为在设置颜色时不需要颜色转换(因为它与图像的颜色模型匹配).

另一个诱人的选择是使用color.Gray颜色(实现color.Color界面),并按照我们现在的方式对颜色进行建模:使用Y,存储uint8.替代方案可能color.Gray16基本上是"相同",但使用16位存储Yuint16.对于这些,最好的还是使用具有匹配颜色模型的图像,例如image.Grayimage.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)
Run Code Online (Sandbox Code Playgroud)

请注意,我们需要将R,G,B组件转换float64为能够乘以权重.因为r,g,b已经是类型uint32,我们可以用整数运算(不溢出)代替这一点.

没有详细说明 - 并且因为标准的lib已经有了解决方案 - 这里是:

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)})
Run Code Online (Sandbox Code Playgroud)

现在没有编写这样"丑陋"的东西,推荐的方法是简单地使用image/color包的颜色转换器,称为Models.准备好的color.GrayModel模型能够将任何颜色转换为模型color.Gray.

这很简单:

oldPixel := img.At(x, y)
pixel := color.GrayModel.Convert(oldPixel)
imgSet.Set(x, y, pixel)
Run Code Online (Sandbox Code Playgroud)

它与我们的最后一个光度加权模型一样,使用整数运算.或者在一行中:

imgSet.Set(x, y, color.GrayModel.Convert(img.At(x, y)))
Run Code Online (Sandbox Code Playgroud)

要获得更高的16位灰度分辨率:

imgSet.Set(x, y, color.Gray16Model.Convert(img.At(x, y)))
Run Code Online (Sandbox Code Playgroud)

最后一点:由于您正在绘制返回的图像image.NewRGBA(),因此它属于类型*image.RGBA.您不需要检查它是否有Set()方法,因为它image.RGBA是一个静态类型(不是接口),并且它有一个Set()方法,它在编译时检查.你需要检查的情况是你是否有一个通用image.Image类型的图像是一个接口,但是这个接口不包含/"规定"该Set()方法; 但是实现该接口的动态类型可以提供.

  • 很好的答案!我不想进入光度,但我可能应该包括`GrayModel`。 (2认同)