iosswiftimage-processingcore-imageimage-comparison

Image comparison to identity and map identical pixels


I'm building this for iOS using Swift — either via CoreImage or GPUImage, but if I can build it in Python or Node/JavaScript, that'd work too. Feel free to answer abstractly, or in a different language entirely — I'll accept any answer that roughly describes how I might go about accomplishing this.

Consider the following two "images" (I've fabricated two 3x3-pixel grids to represent two images, each 3x3 pixels for a total of 9 pixels).

Let's assume I process the original image (left) with a shader that changes the color of some, but not all of the pixels. The resulting image on the right is the same, but for 3 pixels — #2, #3, and #6:

enter image description here

I'm trying to find a means of comparing all of the pixels in both images and logging the x,y position of pixels that haven't changed during the filter process. In this case, when comparing the left to right, I'd need to know that #1, #4, #5, #7, #8, and #9 remained unchanged.


Solution

  • Assuming your images before and after are the same size all you need to do is loop through each pixel and compare them which you can do with a pointer. I certainly don't claim this is the fastest method but it should work (note you can compare all 32 bits at once with a UInt32 pointer, but I am doing it byte wise just to illustrate where the RGBA values are if you need them). Also note that because of the fact that Quartz was written for Mac and it uses Cartesian coordinates and iOS and UIKit do not, its possible your data is upside down (mirrored around the X-axis). You will have to check; it depends on how the internal bitmap is being represented.

      func difference(leftImage: UIImage, rightImage: UIImage) {
          let width = Int(leftImage.size.width)
          let height = Int(leftImage.size.height)
          guard leftImage.size == rightImage.size else {
              return
          }
          if let cfData1:CFData = leftImage.cgImage?.dataProvider?.data,
             let l = CFDataGetBytePtr(cfData1),
             let cfData2:CFData = rightImage.cgImage?.dataProvider?.data,
             let r = CFDataGetBytePtr(cfData2) {
              let bytesPerpixel = 4
              let firstPixel = 0
              let lastPixel = (width * height - 1) * bytesPerpixel
              let range = stride(from: firstPixel, through: lastPixel, by: bytesPerpixel)
              for pixelAddress in range {
                  if l.advanced(by: pixelAddress).pointee != r.advanced(by: pixelAddress).pointee ||     //Red
                     l.advanced(by: pixelAddress + 1).pointee != r.advanced(by: pixelAddress + 1).pointee || //Green
                     l.advanced(by: pixelAddress + 2).pointee != r.advanced(by: pixelAddress + 2).pointee || //Blue
                     l.advanced(by: pixelAddress + 3).pointee != r.advanced(by: pixelAddress + 3).pointee  {  //Alpha
                      print(pixelAddress)
                      // do stuff here
                  }
              }
          }
      }
    

    If you need a faster method write a shader that will delta each pixel and write the result out to a texture. Any pixels that are not clear black (i.e. 0,0,0,0) in the output are different between the images. Shaders are not my area of expertise so I will leave it to someone else to write. Also on some architectures its expensive to read back form graphics memory so you will have to test and see if this is really better than doing it in main memory (may also depend on image size because you have to amortize the setup cost for the textures and shaders).