'How to compare images with go?

In go image package, i don't see any methods which can be used to compare two images? Is it possible to do image comparison in go similar to ImageMagick?

go


Solution 1:[1]

If you are trying to compare two images and just need to boil it down to a single number, the following will work. This is useful in (for example) genetic algorithms, where you want to compare a set of candidates and choose the one that differs the least from a reference image:

  1. Visit every pixel, break it down into its parts: R, G, B, & A (in go: image.At(x,y).RGBA())
  2. Subtract the RGBA vals from their corresponding pixel vals in the reference image.
  3. Square the differences, add them up.
  4. Take the square root of the total sum.

This number will give you a rough idea of how much the images differ.

If you know that the two images are both instances of image.RGBA (or you can convert them), then you can do something even faster: just grab the bytes directly from RGBA.Pix. That's what I do here, and it's roughly 10x faster than doing img.At(x,y).RGBA() for every pixel pair:

func FastCompare(img1, img2 *image.RGBA) (int64, error) {
    if img1.Bounds() != img2.Bounds() {
        return 0, fmt.Errorf("image bounds not equal: %+v, %+v", img1.Bounds(), img2.Bounds())
    }

    accumError := int64(0)

    for i := 0; i < len(img1.Pix); i++ {
        accumError += int64(sqDiffUInt8(img1.Pix[i], img2.Pix[i]))
    }

    return int64(math.Sqrt(float64(accumError))), nil
}

func sqDiffUInt8(x, y uint8) uint64 {   
    d := uint64(x) - uint64(y)
    return d * d
}

Solution 2:[2]

Try https://github.com/vitali-fedulov/images3. I wrote this package to be able to find near duplicates. There is a live web-demo with the same algorithm, so you can get an idea how well the package suites your needs.

Solution 3:[3]

Inspired by George's answer. The function below is not so fast, but it allows you to visually assess the difference in images.

func ImgCompare(img1, img2 image.Image) (int64, image.Image, error) {
    bounds1 := img1.Bounds()
    bounds2 := img2.Bounds()
    if bounds1 != bounds2 {
        return math.MaxInt64, nil, fmt.Errorf("image bounds not equal: %+v, %+v", img1.Bounds(), img2.Bounds())
    }

    accumError := int64(0)
    resultImg := image.NewRGBA(image.Rect(
        bounds1.Min.X,
        bounds1.Min.Y,
        bounds1.Max.X,
        bounds1.Max.Y,
    ))
    draw.Draw(resultImg, resultImg.Bounds(), img1, image.Point{0, 0}, draw.Src)

    for x := bounds1.Min.X; x < bounds1.Max.X; x++ {
        for y := bounds1.Min.Y; y < bounds1.Max.Y; y++ {
            r1, g1, b1, a1 := img1.At(x, y).RGBA()
            r2, g2, b2, a2 := img2.At(x, y).RGBA()

            diff := int64(sqDiffUInt32(r1, r2))
            diff += int64(sqDiffUInt32(g1, g2))
            diff += int64(sqDiffUInt32(b1, b2))
            diff += int64(sqDiffUInt32(a1, a2))

            if diff > 0 {
                accumError += diff
                resultImg.Set(
                    bounds1.Min.X+x,
                    bounds1.Min.Y+y,
                    color.RGBA{R: 255, A: 255})
            }
        }
    }

    return int64(math.Sqrt(float64(accumError))), resultImg, nil
}

func sqDiffUInt32(x, y uint32) uint64 {
    d := uint64(x) - uint64(y)
    return d * d
}

Solution 4:[4]

With two of the current answers here, the images need to be the same size, or the comparison fails. A third answer here uses vitali-fedulov/images, which doesn't have any method to get the difference between two images, only a Similar function that returns a bool determining if two images are similar. Further, the answer at Rosetta Code also fails if the images are different sizes.

So if I was to implement my own solution, first I would need to scale down the larger image. I found x/image/draw and nfnt/resize for that purpose, but I thought maybe I could find something, to kill two birds with one stone. To that end, I did find some packages that scale the images as needed, take a hash of each, and get the difference of the hashes. Here is corona10/goimagehash:

package main

import (
   "github.com/corona10/goimagehash"
   "image/jpeg"
   "os"
)

func hash(name string) (*goimagehash.ImageHash, error) {
   f, err := os.Open(name)
   if err != nil {
      return nil, err
   }
   defer f.Close()
   i, err := jpeg.Decode(f)
   if err != nil {
      return nil, err
   }
   return goimagehash.AverageHash(i)
}

Example:

package main

func main() {
   a, err := hash("mb.jpg")
   if err != nil {
      panic(err)
   }
   b, err := hash("hqdefault.jpg")
   if err != nil {
      panic(err)
   }
   d, err := a.Distance(b)
   if err != nil {
      panic(err)
   }
   println(d)
}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2
Solution 3 SkyN
Solution 4 Zoe stands with Ukraine