将RGBA图像转换为Grayscale Golang

时间:2017-02-28 18:28:50

标签: go colors rgb image-manipulation grayscale

我目前正致力于将RGBA图像转换为灰度图像。

我之前提出了一个问题,并被引导到以下答案 - Change color of a single pixel - Go lang image

这是我原来的问题 - 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

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

1 个答案:

答案 0 :(得分:20)

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

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

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

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

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

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

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

另一个诱人的选择是使用color.Gray这是一种颜色(实现color.Color界面),并按照我们现在的方式对颜色进行建模:使用Y,存储使用uint8。替代方案可以是color.Gray16,它基本上是“相同的”,但使用16位来存储Y作为uint16。对于这些,最好的方法是使用具有匹配颜色模型的图像,例如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)

请注意,我们需要将R,G,B组件转换为float64,以便能够乘以权重。由于rgb已经是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)})

现在没有编写这样“丑陋”的东西,推荐的方法是简单地使用image/color包的颜色转换器,称为Model s。准备好的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类型的图像,但此接口不包含/“规定”Set()方法;但实现此接口的动态类型可能会提供这一点。