Сравнение изображений с идентичностью и сопоставление идентичных пикселей
Я создаю это для 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) }
}
}