go - 将 RGBA 图像转换为灰度 Golang

标签 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

我在答案中遗漏了什么吗?任何提示表示赞赏。

最佳答案

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 -> 灰度转换,必须使用以下权重:

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

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

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

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

现在不用写这么“丑”的东西了,推荐的方法就是直接用 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是一个静态类型(不是接口(interface)),它确实有一个 Set()方法,它在编译时检查。您确实需要检查的情况是您是否拥有一般 image.Image 的图像。类型这是一个接口(interface),但这个接口(interface)不包含/“规定”Set()方法;但是实现这个接口(interface)的动态类型仍然可以提供。

关于go - 将 RGBA 图像转换为灰度 Golang,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42516203/

相关文章:

c - 从 TIFF 中提取专色等效项

php - 按颜色对图像排序

python - 值错误: too many values to unpack (expected 3) using PIL

mongodb - Golang - MongoDB (mgo) 检索插入的文件(BSON 不是 GridFS)

go - 我如何在 Go 中对接口(interface) slice 进行 json 解码?

go - 我们应该在 goroutine 中同步变量赋值吗?

matplotlib - 如何使用连续值 `matplotlib` 调色板为 [`seaborn` 散点图着色?]

c - OpenCV 获取/设置 IplImage RGB 值

google-app-engine - GAE 推送队列任务不在测试中运行,生成孤立的 dev_appserver 进程

image - 在Go中将RGB转换为YUV 420P图像