duandang2838 2017-02-28 18:28
浏览 560
已采纳

将RGBA图像转换为灰度Golang

I'm currently working on a program to convert and RGBA image to grayscale.

I asked a question earlier and was directed to the following answer - Change color of a single pixel - Go lang image

Here is my original question - Program to convert RGBA to grayscale Golang

I have edited my code so it now successfully runs - however the image outputted is not what I want. It is converted to grayscale however the pixels are all messed up making it look like noise on an old TV.

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
", "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)

}

I know I haven't added in the if else statement for checking if the image can accept the Set() method, however the suggestion for simply making a new image seems to have solved this.

Any help much appreciated.

Edit:

I've added in some suggested code from the answer below:

    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
", "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)

}

I currently get the following error.

.gbtogray.go:36: cannot use y (type uint32) as type int in argument to imgSet.Set

Am I missing something from the answer? Any tips appreciated.

  • 写回答

1条回答 默认 最新

  • douchenhui5569 2017-02-28 20:50
    关注

    Color.RGBA() is a method that returns the alpha-premultiplied red, green, blue and alpha values, all being of type uint32, but only being in the range of [0, 0xffff] (using only 16 bits out of 32). This means you can add these components, they will not overflow (max value of each component fits into 16 bits, so their sum will fit into 32 bits).

    One thing to note here: the result will also be alpha-premultiplied, and after dividing by 3, it will still be in the range of [0..0xffff]. So by doing a uint8(r) type conversion, you're just keeping the lowest 8 bits, which will be seemingly just a random value compared to the whole number. You should rather take the highest 8 bits.

    But not so fast. What we're trying to do here is convert a color image to a grayscale image, which will lose the "color" information, and what we want is basically the luminosity of each pixel. Your proposed solution is called the average method, and it gives rather poor result, because it takes all the R, G and B components with equal weight, even though these colors have different wavelength and thus contribute in different measures to the luminosity of the overall pixel. Read more about it here: Grayscale to RGB Conversion.

    For a realistic RGB -> grayscale conversion, the following weights have to be used:

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

    You can read more behind these weights (and variants) on wikipedia: Grayscale. This is called the luminosity method, and this will give the best grayscale images.

    So far so good, we have the luminosity, how do we go to a color.Color value from here? One option is to use a color.RGBA color value, where you specify the same luminosity to all components (alpha may be kept). And if you intend to use an image.RGBA returned by image.NewRGBA(), probably this is the best way as no color conversion will be needed when setting the color (as it matches the image's color model).

    Another tempting choice is to use the color.Gray which is a color (implements the color.Color interface), and models a color just the way we have it now: with Y, stored using an uint8. An alternative could be color.Gray16 which is basically the "same", but uses 16 bits to store Y as an uint16. For these, best would be to also use an image with the matching color model, such as image.Gray or image.Gray16 (although this is not a requirement).

    So the conversion should be:

    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)
    

    Note that we needed to convert the R, G, B components to float64 to be able to multiply by the weights. Since r, g, b are already of type uint32, we could substitute this with integer operations (without overflow).

    Without going into details –and because the standard lib already has a solution for this–, here it is:

    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)})
    

    Now without writing such "ugly" things, the recommended way is to simply use the color converters of the image/color package, called Models. The prepared color.GrayModel model is able to convert any colors to the model of color.Gray.

    It's this simple:

    oldPixel := img.At(x, y)
    pixel := color.GrayModel.Convert(oldPixel)
    imgSet.Set(x, y, pixel)
    

    It does the same as our last luminosity weighted model, using integer-arithmetic. Or in one line:

    imgSet.Set(x, y, color.GrayModel.Convert(img.At(x, y)))
    

    To have a higher, 16-bit grayscale resolution:

    imgSet.Set(x, y, color.Gray16Model.Convert(img.At(x, y)))
    

    One last note: since you're drawing on an image returned by image.NewRGBA(), it is of type *image.RGBA. You don't need to check if it has a Set() method, because image.RGBA is a static type (not interface), and it does have a Set() method, it is checked at compile time. The case when you do need to check is if you have an image of the general image.Image type which is an interface, but this interface does not contain / "prescribe" the Set() method; but the dynamic type implementing this interface may provide that nonetheless.

    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论

报告相同问题?

悬赏问题

  • ¥15 有卷积神经网络识别害虫的项目吗
  • ¥15 数据库数据成问号了,前台查询正常,数据库查询是?号
  • ¥15 算法使用了tf-idf,用手肘图确定k值确定不了,第四轮廓系数又太小才有0.006088746097507285,如何解决?(相关搜索:数据处理)
  • ¥15 彩灯控制电路,会的加我QQ1482956179
  • ¥200 相机拍直接转存到电脑上 立拍立穿无线局域网传
  • ¥15 (关键词-电路设计)
  • ¥15 如何解决MIPS计算是否溢出
  • ¥15 vue中我代理了iframe,iframe却走的是路由,没有显示该显示的网站,这个该如何处理
  • ¥15 操作系统相关算法中while();的含义
  • ¥15 CNVcaller安装后无法找到文件