图像与身份的比较并映射相同的像素

Image comparison to identity and map identical pixels

我正在使用 Swift 为 iOS 构建它 — 通过 CoreImage 或 GPUImage,但如果我可以在 Python 或 [=26 中构建它=],那也行。请随意抽象地回答,或者完全用不同的语言回答——我会接受任何粗略描述我可能如何实现这一目标的回答。

考虑以下两个 "images"(我制作了两个 3x3 像素的网格来表示两个图像,每个 3x3 像素,总共 9 个像素)。

假设我使用着色器处理原始图像(左),该着色器改变了一些但不是所有像素的颜色。右边的结果图像是相同的,但有 3 个像素 — #2、#3 和 #6:

我试图找到一种方法来比较两个图像中的所有像素并记录在过滤过程中没有改变的像素的 x,y 位置.在这种情况下,当比较左右时,我需要知道#1、#4、#5、#7、#8 和#9 保持不变。

假设你之前和之后的图像大小相同,你需要做的就是遍历每个像素并比较它们,你可以用指针来做。我当然不是说这是最快的方法,但它应该可以工作(请注意,您可以一次将所有 32 位与 UInt32 指针进行比较,但我这样做只是为了在需要时说明 RGBA 值的位置) .另请注意,由于 Quartz 是为 Mac 编写的并且它使用笛卡尔坐标,而 iOS 而 UIKit 不使用,因此您的数据可能是颠倒的(围绕 X 轴镜像)。您将不得不检查;这取决于内部位图的表示方式。

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

如果您需要更快的方法,请编写一个着色器,该着色器将对每个像素进行增量处理并将结果写入纹理。输出中任何不清晰的黑色像素(即 0,0,0,0)在图像之间是不同的。着色器不是我的专业领域,所以我会把它留给别人来写。同样在某些体系结构上,从图形内存中读回的成本很高,因此您必须测试并查看这是否真的比在主内存中执行更好(也可能取决于图像大小,因为您必须分摊纹理的设置成本和着色器)。

我使用另一个选项,一个稍微修改过的 Facebook 版本。

原代码here

func compareWithImage(_ referenceImage: UIImage, tolerance: CGFloat = 0) -> Bool {
    guard size.equalTo(referenceImage.size) else {
        return false
    }
    guard let cgImage = cgImage, let referenceCGImage = referenceImage.cgImage else {
        return false
    }
    let minBytesPerRow = min(cgImage.bytesPerRow, referenceCGImage.bytesPerRow)
    let referenceImageSizeBytes = Int(referenceImage.size.height) * minBytesPerRow
    let imagePixelsData = UnsafeMutablePointer<Pixel>.allocate(capacity: cgImage.width * cgImage.height)
    let referenceImagePixelsData = UnsafeMutablePointer<Pixel>.allocate(capacity: cgImage.width * cgImage.height)

    let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.premultipliedLast.rawValue & CGBitmapInfo.alphaInfoMask.rawValue)

    guard let colorSpace = cgImage.colorSpace, let referenceColorSpace = referenceCGImage.colorSpace else { return false }

    guard let imageContext = CGContext(data: imagePixelsData, width: cgImage.width, height: cgImage.height, bitsPerComponent: cgImage.bitsPerComponent, bytesPerRow: minBytesPerRow, space: colorSpace, bitmapInfo: bitmapInfo.rawValue) else { return false }
    guard let referenceImageContext = CGContext(data: referenceImagePixelsData, width: referenceCGImage.width, height: referenceCGImage.height, bitsPerComponent: referenceCGImage.bitsPerComponent, bytesPerRow: minBytesPerRow, space: referenceColorSpace, bitmapInfo: bitmapInfo.rawValue) else { return false }

    imageContext.draw(cgImage, in: CGRect(x: 0, y: 0, width: size.width, height: size.height))
    referenceImageContext.draw(referenceCGImage, in: CGRect(x: 0, y: 0, width: referenceImage.size.width, height: referenceImage.size.height))

    var imageEqual = true

    // Do a fast compare if we can
    if tolerance == 0 {
        imageEqual = memcmp(imagePixelsData, referenceImagePixelsData, referenceImageSizeBytes) == 0
    } else {
        // Go through each pixel in turn and see if it is different
        let pixelCount = referenceCGImage.width * referenceCGImage.height

        let imagePixels = UnsafeMutableBufferPointer<Pixel>(start: imagePixelsData, count: cgImage.width * cgImage.height)
        let referenceImagePixels = UnsafeMutableBufferPointer<Pixel>(start: referenceImagePixelsData, count: referenceCGImage.width * referenceCGImage.height)

        var numDiffPixels = 0
        for i in 0..<pixelCount {
            // If this pixel is different, increment the pixel diff count and see
            // if we have hit our limit.
            let p1 = imagePixels[i]
            let p2 = referenceImagePixels[i]

            if p1.value != p2.value {
                numDiffPixels += 1

                let percents = CGFloat(numDiffPixels) / CGFloat(pixelCount)
                if percents > tolerance {
                    imageEqual = false
                    break
                }
            }
        }
    }

    free(imagePixelsData)
    free(referenceImagePixelsData)

    return imageEqual
}

struct Pixel {

    var value: UInt32

    var red: UInt8 {
        get { return UInt8(value & 0xFF) }
        set { value = UInt32(newValue) | (value & 0xFFFFFF00) }
    }

    var green: UInt8 {
        get { return UInt8((value >> 8) & 0xFF) }
        set { value = (UInt32(newValue) << 8) | (value & 0xFFFF00FF) }
    }

    var blue: UInt8 {
        get { return UInt8((value >> 16) & 0xFF) }
        set { value = (UInt32(newValue) << 16) | (value & 0xFF00FFFF) }
    }

    var alpha: UInt8 {
        get { return UInt8((value >> 24) & 0xFF) }
        set { value = (UInt32(newValue) << 24) | (value & 0x00FFFFFF) }
    }

}