我目前正在开发一个将 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/