Сравнение изображений с идентичностью и сопоставление идентичных пикселей

Я создаю это для iOS, используя Swift - либо через CoreImage, либо через GPUImage, но если я смогу построить его на Python или Node/JavaScript, это тоже сработает. Не стесняйтесь отвечать абстрактно или на другом языке целиком - я приму любой ответ, который примерно описывает, как я мог бы сделать это.

Рассмотрим следующие два "изображения" (я изготовил две сетки 3x3 пикселя, чтобы представить два изображения, каждое 3x3 пикселя, всего 9 пикселей).

Давайте предположим, что я обрабатываю исходное изображение (слева) с помощью шейдера, который меняет цвет некоторых, но не всех пикселей. Результирующее изображение справа такое же, но для 3 пикселей - #2, #3 и #6:

Я пытаюсь найти способ сравнить все пиксели на обоих изображениях и записать координаты x, y пикселей, которые не изменились в процессе фильтрации. В этом случае, при сравнении слева направо, мне нужно знать, что № 1, № 4, № 5, № 7, № 8 и № 9 остались без изменений.

2 ответа

Решение

Предполагая, что ваши изображения до и после имеют одинаковый размер, все, что вам нужно сделать, это перебрать каждый пиксель и сравнить их, что вы можете сделать с помощью указателя. Я, конечно, не утверждаю, что это самый быстрый метод, но он должен работать (обратите внимание, вы можете сравнить все 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.

Оригинальный код здесь

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

}
Другие вопросы по тегам