【问题标题】:How to compare two UIImage objects如何比较两个 UIImage 对象
【发布时间】:2012-07-05 10:55:45
【问题描述】:

我正在开发一个应用程序。在更改 UIImageview 图像之前,我正在使用imageviews.SO,我需要在UIimage obejct 中获取该图像并与另一个UIImage 对象进行比较以找到两者山姆与否。所以请告诉我该怎么做。

【问题讨论】:

  • 接受的答案在 [this][1] 线程 [1]:stackoverflow.com/questions/3400707/…
  • 您应该维护应用程序状态并拥有数据模型。通过这种方式,您将对应用中发生的事情有所了解。

标签: ios


【解决方案1】:

一种方法是先将它们转换为图像数据,然后进行比较。

- (BOOL)image:(UIImage *)image1 isEqualTo:(UIImage *)image2
{
    NSData *data1 = UIImagePNGRepresentation(image1);
    NSData *data2 = UIImagePNGRepresentation(image2);

    return [data1 isEqual:data2];
}

【讨论】:

  • 这个最好作为一个类别来实现
  • 应该使用isEqualToData:吗?合理的问题 - 我不确定 isEqual: 何时或是否会因 NSData 对象而失败,但对我来说使用专门为该类设计的条件测试似乎是合乎逻辑的,对吧?
  • UIImagePNGRepresentation 被破坏,因为它没有考虑方向。具有不同方向的两个逻辑相同的图像可能无法通过此检查。
  • 如果其中一个图像保存在 CoreDate(作为 NSData)中,而另一个 id 为 imageNamed,则无法工作
  • 比较吃的挺费时间的。
【解决方案2】:

@Simon 答案的 Swift 实现:

func image(image1: UIImage, isEqualTo image2: UIImage) -> Bool {
    let data1: NSData = UIImagePNGRepresentation(image1)!
    let data2: NSData = UIImagePNGRepresentation(image2)!
    return data1.isEqual(data2)
}

或者根据@nhgrif 的建议扩展 UIImage:

import UIKit

extension UIImage {

    func isEqualToImage(image: UIImage) -> Bool {
        let data1: NSData = UIImagePNGRepresentation(self)!
        let data2: NSData = UIImagePNGRepresentation(image)!
        return data1.isEqual(data2)
    }

}

【讨论】:

  • 是的,我同意,但是我只是从另一个答案翻译代码
  • 更新答案以包含您的建议示例@nhgrif
  • 现在我唯一的评论是,与其强制解包和崩溃,我们应该返回false,如果其中一个是nilguard let data1 = UIImagePNGRepresentation(self), data2 = UIImagePNGRepresentation(image) else { return false } return data1 == data2
  • 在 swift 4 中需要这样写 let data1: NSData = UIImagePNGRepresentation(image1)!作为 NSData
  • 通过转换为 Swift 4 发布了对此答案的更新
【解决方案3】:

更新了 Mark Tickner 对 Swift 4 的解决方案

import UIKit

extension UIImage {

    func isEqualToImage(_ image: UIImage) -> Bool {
        let data1 = self.pngData()
        let data2 = image.pngData()
        return data1 == data2
    }

}

这两个变量可能有点矫枉过正,但它们可能有助于向对此不熟悉的人解释。可以缩短为:

import UIKit

extension UIImage {

    func isEqualToImage(_ image: UIImage) -> Bool {
        return self.pngData() == image.pngData()
    }

}

【讨论】:

  • @YogeshPatel 我无法发表评论,您必须尝试一下。如果您这样做,请在此处更新我们!
  • 我已经这样做了,但它不适合我。这就是我告诉你的原因。你能帮帮我吗!
【解决方案4】:

当两者都使用[UIImage imageNamed:]时,我们可以使用isEqual:,否则我们可以比较数据。

【讨论】:

  • 这不是一个真正安全的解决方案,尤其是在 UITableView 中使用它时。我建议使用@Simon 的解决方案。
  • @Sebyddd 真的吗?在渲染 UITableViewCells 时您是否尝试过使用 simon 的解决方案?比较操作非常昂贵..所以滚动会很不稳定..真实故事
  • @abbood 是的,它可能更贵,但从我的测试来看,由于某种原因,它并不总是返回正确的结果。如果两个 NSObject 指向相同的内存地址,则它们被认为是相等的。这就是 isEqual 所做的。因此 imageNamed 在不同的内存地址返回一个新实例。它不是很稳定。
  • @Sebyddd 继承自isEqual: 的默认实现NSObject 是一个简单的参考比较,是的。但是,某些对象会覆盖此默认实现。例如,我肯定知道的是NSString,其isEqual: 在功能上等同于isEqualToString:,两者都是值比较——而不是参考比较。我无法评论UIImageisEqual 的实现(它是否覆盖了继承的方法)。
【解决方案5】:

我的首选 (Swift) 解决方案

import UIKit

func ==(lhs: UIImage, rhs: UIImage) -> Bool
{
  guard let data1 = UIImagePNGRepresentation(lhs),
            data2 = UIImagePNGRepresentation(rhs)
    else { return false }

  return data1.isEqual(data2)
}

【讨论】:

  • 使用 isEqual 的默认实现对我也不起作用,这是唯一对我有用的解决方案。
【解决方案6】:

正确答案取决于“您要进行哪种比较?”。

  1. 最简单的方法就是比较数据。
  2. 如果您想知道图像是否是从一个本地文件创建的 - 您可以使用 -isEqual:(但有一种危险的方法,因为我不确定,如果图像缓存会发生什么出于某种原因清除)。
  3. 困难的方法是提供逐像素比较(当然,系统会花更多时间在这上面)。由于法律原因,我无法提供我们公司库中的代码:(

但是你可以在这里查看 facebook 的 ios-snapshot-test-case 项目中的好例子:link right to the needed file。 您可以使用性能测试来衡量流程时间。

为了伟大的正义,我将从下面复制代码:

- (BOOL)fb_compareWithImage:(UIImage *)image tolerance:(CGFloat)tolerance
{
  NSAssert(CGSizeEqualToSize(self.size, image.size), @"Images must be same size.");

  CGSize referenceImageSize = CGSizeMake(CGImageGetWidth(self.CGImage), CGImageGetHeight(self.CGImage));
  CGSize imageSize = CGSizeMake(CGImageGetWidth(image.CGImage), CGImageGetHeight(image.CGImage));

  // The images have the equal size, so we could use the smallest amount of bytes because of byte padding
  size_t minBytesPerRow = MIN(CGImageGetBytesPerRow(self.CGImage), CGImageGetBytesPerRow(image.CGImage));
  size_t referenceImageSizeBytes = referenceImageSize.height * minBytesPerRow;
  void *referenceImagePixels = calloc(1, referenceImageSizeBytes);
  void *imagePixels = calloc(1, referenceImageSizeBytes);

  if (!referenceImagePixels || !imagePixels) {
    free(referenceImagePixels);
    free(imagePixels);
    return NO;
  }

  CGContextRef referenceImageContext = CGBitmapContextCreate(referenceImagePixels,
                                                             referenceImageSize.width,
                                                             referenceImageSize.height,
                                                             CGImageGetBitsPerComponent(self.CGImage),
                                                             minBytesPerRow,
                                                             CGImageGetColorSpace(self.CGImage),
                                                             (CGBitmapInfo)kCGImageAlphaPremultipliedLast
                                                             );
  CGContextRef imageContext = CGBitmapContextCreate(imagePixels,
                                                    imageSize.width,
                                                    imageSize.height,
                                                    CGImageGetBitsPerComponent(image.CGImage),
                                                    minBytesPerRow,
                                                    CGImageGetColorSpace(image.CGImage),
                                                    (CGBitmapInfo)kCGImageAlphaPremultipliedLast
                                                    );

  if (!referenceImageContext || !imageContext) {
    CGContextRelease(referenceImageContext);
    CGContextRelease(imageContext);
    free(referenceImagePixels);
    free(imagePixels);
    return NO;
  }

  CGContextDrawImage(referenceImageContext, CGRectMake(0, 0, referenceImageSize.width, referenceImageSize.height), self.CGImage);
  CGContextDrawImage(imageContext, CGRectMake(0, 0, imageSize.width, imageSize.height), image.CGImage);

  CGContextRelease(referenceImageContext);
  CGContextRelease(imageContext);

  BOOL imageEqual = YES;

  // Do a fast compare if we can
  if (tolerance == 0) {
    imageEqual = (memcmp(referenceImagePixels, imagePixels, referenceImageSizeBytes) == 0);
  } else {
    // Go through each pixel in turn and see if it is different
    const NSInteger pixelCount = referenceImageSize.width * referenceImageSize.height;

    FBComparePixel *p1 = referenceImagePixels;
    FBComparePixel *p2 = imagePixels;

    NSInteger numDiffPixels = 0;
    for (int n = 0; n < pixelCount; ++n) {
      // If this pixel is different, increment the pixel diff count and see
      // if we have hit our limit.
      if (p1->raw != p2->raw) {
        numDiffPixels ++;

        CGFloat percent = (CGFloat)numDiffPixels / pixelCount;
        if (percent > tolerance) {
          imageEqual = NO;
          break;
        }
      }

      p1++;
      p2++;
    }
  }

  free(referenceImagePixels);
  free(imagePixels);

  return imageEqual;
}

【讨论】:

    【解决方案7】:

    将图像转换为 JPG / PNG,或依赖可访问性标识符要么是一项昂贵的操作,要么是脆弱的且容易失败。

    这里,我按照苹果在following link提供的建议:

    isEqual(:) 方法是唯一可靠的方法来确定是否 两个图像包含相同的图像数据。您创建的图像对象 可能彼此不同,即使您使用 相同的缓存图像数据。确定他们平等的唯一方法 就是使用isEqual(:)方法,比较实际图片 数据。清单 1 说明了正确和错误的比较方法 图片。

    为了简化事情,我创建了以下扩展来进行比较,这样我就可以避免转换第一张图片的问题:

    import UIKit
    
    extension UIImage {
        func isEqual(to image: UIImage) -> Bool {
            return isEqual(image)
        }
    }
    

    有了这个,我现在可以设置一个示例来在一对图像上进行比较:

    let imageA = UIImage(named: "a")!
    let imageB = UIImage(named: "b")!
    let imageC = UIImage(named: "a")!
    
    print(imageA.isEqual(to: imageA)) // true
    print(imageA.isEqual(to: imageC)) // true
    print(imageA.isEqual(to: imageB)) // false
    

    【讨论】:

    • 尽管有Apple的文档,但我发现isEqual没有正确比较Obj-C中两个UIImage实例的数据;它为包含相同数据的两个 UIImage 实例返回 false。将 UIImage 实例转换为 PNG 并比较原始数据(正如 Simon 的回答中所做的那样)是我发现正确与 Obj-C 进行比较的唯一方法。
    【解决方案8】:

    我对 Mark 的回答做了一些更改,使用 Data 和 elementsEqual 代替 NSData 和 isEqual。

    extension UIImage {
        func isEqual(to image: UIImage) -> Bool {
            guard let data1: Data = UIImagePNGRepresentation(self),
                let data2: Data = UIImagePNGRepresentation(image) else {
                    return false
            }
            return data1.elementsEqual(data2)
        }
    }
    

    【讨论】:

      【解决方案9】:

      Swift 4.x 版本的 Facebook 比较算法:

      /// Value in range 0...100 %
      typealias Percentage = Float
      
      // See: https://github.com/facebookarchive/ios-snapshot-test-case/blob/master/FBSnapshotTestCase/Categories/UIImage%2BCompare.m
      private func compare(tolerance: Percentage, expected: Data, observed: Data) throws -> Bool {
          guard let expectedUIImage = UIImage(data: expected), let observedUIImage = UIImage(data: observed) else {
              throw Error.unableToGetUIImageFromData
          }
          guard let expectedCGImage = expectedUIImage.cgImage, let observedCGImage = observedUIImage.cgImage else {
              throw Error.unableToGetCGImageFromData
          }
          guard let expectedColorSpace = expectedCGImage.colorSpace, let observedColorSpace = observedCGImage.colorSpace else {
              throw Error.unableToGetColorSpaceFromCGImage
          }
          if expectedCGImage.width != observedCGImage.width || expectedCGImage.height != observedCGImage.height {
              throw Error.imagesHasDifferentSizes
          }
          let imageSize = CGSize(width: expectedCGImage.width, height: expectedCGImage.height)
          let numberOfPixels = Int(imageSize.width * imageSize.height)
      
          // Checking that our `UInt32` buffer has same number of bytes as image has.
          let bytesPerRow = min(expectedCGImage.bytesPerRow, observedCGImage.bytesPerRow)
          assert(MemoryLayout<UInt32>.stride == bytesPerRow / Int(imageSize.width))
      
          let expectedPixels = UnsafeMutablePointer<UInt32>.allocate(capacity: numberOfPixels)
          let observedPixels = UnsafeMutablePointer<UInt32>.allocate(capacity: numberOfPixels)
      
          let expectedPixelsRaw = UnsafeMutableRawPointer(expectedPixels)
          let observedPixelsRaw = UnsafeMutableRawPointer(observedPixels)
      
          let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.premultipliedLast.rawValue)
          guard let expectedContext = CGContext(data: expectedPixelsRaw, width: Int(imageSize.width), height: Int(imageSize.height),
                                                bitsPerComponent: expectedCGImage.bitsPerComponent, bytesPerRow: bytesPerRow,
                                                space: expectedColorSpace, bitmapInfo: bitmapInfo.rawValue) else {
              expectedPixels.deallocate()
              observedPixels.deallocate()
              throw Error.unableToInitializeContext
          }
          guard let observedContext = CGContext(data: observedPixelsRaw, width: Int(imageSize.width), height: Int(imageSize.height),
                                                bitsPerComponent: observedCGImage.bitsPerComponent, bytesPerRow: bytesPerRow,
                                                space: observedColorSpace, bitmapInfo: bitmapInfo.rawValue) else {
              expectedPixels.deallocate()
              observedPixels.deallocate()
              throw Error.unableToInitializeContext
          }
      
          expectedContext.draw(expectedCGImage, in: CGRect(origin: .zero, size: imageSize))
          observedContext.draw(observedCGImage, in: CGRect(origin: .zero, size: imageSize))
      
          let expectedBuffer = UnsafeBufferPointer(start: expectedPixels, count: numberOfPixels)
          let observedBuffer = UnsafeBufferPointer(start: observedPixels, count: numberOfPixels)
      
          var isEqual = true
          if tolerance == 0 {
              isEqual = expectedBuffer.elementsEqual(observedBuffer)
          } else {
              // Go through each pixel in turn and see if it is different
              var numDiffPixels = 0
              for pixel in 0 ..< numberOfPixels where expectedBuffer[pixel] != observedBuffer[pixel] {
                  // If this pixel is different, increment the pixel diff count and see if we have hit our limit.
                  numDiffPixels += 1
                  let percentage = 100 * Float(numDiffPixels) / Float(numberOfPixels)
                  if percentage > tolerance {
                      isEqual = false
                      break
                  }
              }
          }
      
          expectedPixels.deallocate()
          observedPixels.deallocate()
      
          return isEqual
      }
      

      【讨论】:

        【解决方案10】:

        斯威夫特 3

        有两种方法。喜欢:-

        1) 使用 isEqual() 函数。

         self.image?.isEqual(UIImage(named: "add-image"))
        

        2) 使用accessibilityIdentifier

        将accessibilityIdentifier设置为图片名称

        myImageView.image?.accessibilityIdentifier = "add-image"
        

        然后使用以下代码。

        extension UIImageView
        {
        func getFileName() -> String? {
        // First set accessibilityIdentifier of image before calling.
        let imgName = self.image?.accessibilityIdentifier
        return imgName
        }
        }
        

        最后,识别方法的调用方式

        myImageView.getFileName()
        

        【讨论】:

        • myImageView.getFileName() -> 总是返回 Nil.. 我已经设置了可访问性标识符,但这个方法 getFileName 总是返回 nil。
        • 对不起,阿卜杜勒,这些答案在 Swift 3.0 iOS 10.x 中都不适合我
        • @YashBedi 在 vi​​ewdidload 中添加这个 myImageView.image?.accessibilityIdentifier = "add-image"。
        【解决方案11】:

        Swift 5.x

        你可以使用

        let image: UIImage!
        let anotherImage: UIImage!
        
        image == anotherImage
        

        【讨论】:

          【解决方案12】:

          初步比较图片大小

          对于较便宜的方法,首先比较图像大小。即使图像内部有微小的变化,大小也会有所不同。

          NSData *data1 = UIImagePNGRepresentation(image1);
          NSData *data2 = UIImagePNGRepresentation(image2);
          
          if(data1.length == data2.length) {
              // if required, compare the data to confirm it
              if(data1 isEqual:data2) {
                  // images are exactly same
              } else {
                  // even though size is same images are different
              }
          } else {
              // images are different.
          }
          

          在比较来自相同来源(相同尺寸、格式等)的图像时成功测试。

          【讨论】:

          • 我确信 isEqual 一开始就是这样做的,所以也许不需要手动检查长度?
          • 我刚刚查看了汇编,NSData.isEqual 实际上调用了NSData.isEqualToData,它确实在检查内存内容之前检查了长度(它通过调用memcmp 来做到这一点)。因此,您可以调用 data1.isEqual(data2) 而不会影响性能。
          • @andy 你能用记录时间戳来测试一下并分享数据吗?请尝试循环拍摄屏幕截图,假设每秒 10 个屏幕截图,并记录时间戳及其在两种情况下的差异。
          • 是的,这是不必要的,相信内部 isEqual 实现会高效完成,除非您能证明其他方式。
          【解决方案13】:

          这个方法效果很好:

          func isEqualImages(image1: UIImage, image2: UIImage) -> Bool {
              let data1: Data? = UIImagePNGRepresentation(image1)
              let data2: Data? = UIImagePNGRepresentation(image2)
              return data1 == data2
          }
          

          【讨论】:

            【解决方案14】:

            快捷快速的解决方案

            extension UIImage: Equatable {
                static func ==(lhs: UIImage, rhs: UIImage) -> Bool {
                    return lhs.pngData() == rhs.pngData()
                }
            }
            

            【讨论】:

              【解决方案15】:

              我需要检测视频馈送帧中的差异并阈值该差异,以便决定在我的代码中执行某些操作。您可以使用类似的像素比较来查看 UIImage 的 pngData。

              看我的回答here

              【讨论】:

                【解决方案16】:

                我不确定比较 UIImage 数据,因为它会非常昂贵。你可以做的是继承 Uimage 并添加你自己的标签属性,然后在更改图像之前比较标签。分配

                【讨论】:

                  【解决方案17】:

                  如果您知道一个图像名称,它将对您有所帮助....

                  CGImageRef cgImage = [imageView.image CGImage];
                  if (cgImage == [UIImage imageNamed:@"imagename.png"].CGImage) {
                     // True Statement
                  }
                  

                  【讨论】:

                  • CGImageRef 是一个指针,比较两个指针并不能保证图像的相等性。 UIImage.image(named:) 缓存图像,因此这可能会起作用,因为您使用该方法,例如您两次检索相同的图像并进行比较。但这可能会在内存不足警告的情况下中断。
                  【解决方案18】:

                  您还可以使用“标记”属性来识别程序后期阶段的对象。只需设置整数值就可以了

                  【讨论】:

                  • UIImage 没有标签属性。你一定把 OP 误认为 UIImageViews 了。
                  【解决方案19】:

                  我们需要在 Objective-C 中使用 isEqualToData 方法。苹果文件指出

                  Two data objects are equal if they hold the same number of bytes, and if the bytes at the same position in the objects are the same.

                  - (BOOL)image:(UIImage *)image1 isEqualTo:(UIImage *)image2
                  {
                      NSData *data1 = UIImagePNGRepresentation(image1);
                      NSData *data2 = UIImagePNGRepresentation(image2);
                  
                      return [data1 isEqualToData:data2];
                  }
                  

                  【讨论】:

                    猜你喜欢
                    • 1970-01-01
                    • 1970-01-01
                    • 2017-03-08
                    • 1970-01-01
                    • 1970-01-01
                    • 2017-11-29
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    相关资源
                    最近更新 更多