我目前正致力于将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
我错过了答案吗?任何提示赞赏。
答案 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.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
类型,我们可以用整数运算替换它(没有溢出)。
没有详细说明 - 并且因为标准的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()
方法;但实现此接口的动态类型可能会提供这一点。