2012-06-12 10 views
12

Estoy trabajando en una aplicación Java que toma una imagen como una matriz de bytes, la lee en una instancia java.awt.image.BufferedImage y la pasa a una biblioteca de terceros para su procesamiento.¿Existe una manera simple de comparar instancias de BufferedImage?

Para una prueba de unidad, quiero tomar una imagen (de un archivo en el disco) y afirmar que es igual a la misma imagen que ha sido procesada por el código.

  • Mi esperaBufferedImage se lee de un archivo PNG en el disco utilizando ImageIO.read(URL).
  • Mi prueba código lee el mismo archivo en un BufferedImage y lo escribe en una matriz de bytes como PNG para proporcionar al sistema bajo prueba.

Cuando el sistema bajo prueba escribe el conjunto de bytes en un nuevo BufferedImage, deseo afirmar que las dos imágenes son iguales de manera significativa. Usar equals() (heredado de Object) no funciona (por supuesto). La comparación de los valores BufferedImage.toString() tampoco funciona porque la cadena de salida incluye información de referencia del objeto.

¿Alguien sabe algún atajo? Preferiría no traer una biblioteca de terceros para una prueba de unidad única en una pequeña parte de una aplicación grande.

+0

¿Podría explicar por qué exactamente '.equals()' no funcionará? –

+1

@JakeKing: si se hereda de Object, no funcionará porque eso solo hace la identidad del objeto. – Thilo

+2

¿no puedes simplemente comparar las matrices de bytes (que contienen el PNG)? – Thilo

Respuesta

13

Este es el mejor enfoque. No es necesario mantener una variable para saber si la imagen es igual. Simplemente devuelva falso inmediatamente cuando la condición sea falsa.La evaluación de cortocircuito ayuda a ahorrar tiempo al pasar por encima de los píxeles después de que la comparación falla, como es el caso en answer de trumpetlick.

/** 
* Compares two images pixel by pixel. 
* 
* @param imgA the first image. 
* @param imgB the second image. 
* @return whether the images are both the same or not. 
*/ 
public static boolean compareImages(BufferedImage imgA, BufferedImage imgB) { 
    // The images must be the same size. 
    if (imgA.getWidth() == imgB.getWidth() && imgA.getHeight() == imgB.getHeight()) { 
    int width = imgA.getWidth(); 
    int height = imgA.getHeight(); 

    // Loop over every pixel. 
    for (int y = 0; y < height; y++) { 
     for (int x = 0; x < width; x++) { 
     // Compare the pixels for equality. 
     if (imgA.getRGB(x, y) != imgB.getRGB(x, y)) { 
      return false; 
     } 
     } 
    } 
    } else { 
    return false; 
    } 

    return true; 
} 
+0

¡solución agradable y simple! ¡Exactamente lo que estaba buscando! – Waylander

3

¡Podrías escribir tu propia rutina para comparar!

int width; 
int height; 
boolean imagesEqual = true; 

if(image1.getWidth() == (width = image2.getWidth()) && 
    image1.getHeight() == (height = image2.getHeight())){ 

    for(int x = 0;imagesEqual == true && x < width; x++){ 
     for(int y = 0;imagesEqual == true && y < height; y++){ 
      if(image1.getRGB(x, y) != image2.getRGB(x, y)){ 
       imagesEqual = false; 
      } 
     } 
    } 
}else{ 
    imagesEqual = false; 
} 

¡Esto sería de una sola manera!

+2

también debe ser falso cuando los tamaños no coinciden. Establezca booleano en verdadero solo dentro del bloque 'if'. – Thilo

+0

¡Great Point, he editado! – trumpetlicks

+1

@trumpetlicks Además, esa declaración 'break' no hará mucho, usted anudó' for' loops. – Jeffrey

0

No puedo pensar en nada, además de una fuerza bruta "hacer bucle":

BufferedImage bi1, bi2, ... 
    ... 
    Raster r1 = bi1.getData(); 
    DataBuffer db1 = r1.getDataBuffer(); 
    if (db1.getSize() != db2.getSize()) 
    ... 
    for (int i = 0; i < db1.getSize(); i++) { 
    int px = db1.getElem(i); 
    } 
+0

¡buena respuesta, pero esto usa el doble de memoria ya que copia la imagen completa en otro buffer! Puede tener la posibilidad de ser más rápido hacer menos llamadas de rutina reales :-) ¡¡¡No hay manera de saber sin probar !!! – trumpetlicks

+0

@trumpetlicks +1 por * "¡No hay forma de saber sin probar!" * Sospecho que será * más rápido * que 'getRGB()' por píxel, pero las pruebas lo ordenarán. –

+0

@AndrewThompson - No estoy seguro, él está llamando a db1.getSize cada iteración, así como potencialmente a 4 copias de datos. 1 para crear r1, otro para db1, y el mismo 2 para r2 y db2. Luego también tiene db1.getElem (i). En realidad, esto será más lento, porque también está llamando a una rutina para obtener ambos elementos dentro de las matrices. db1.getSize() va a devolver (ancho * alto) de la imagen. Así que no solo está copiando los datos, sino llamando a la misma cantidad de rutinas dentro del ciclo. ¡¡¡Ni siquiera está haciendo la operación de comparación en absoluto !!! – trumpetlicks

5

Si la velocidad es un problema, y ​​ambos BufferedImages son de la misma profundidad de bits, arreglo, etc. (que parece que debe ser cierto aquí) puede hacer esto:

DataBuffer dbActual = myBufferedImage.getRaster().getDataBuffer(); 
DataBuffer dbExpected = bufferImageReadFromAFile.getRaster().getDataBuffer(); 

averiguar de qué tipo es, por ejemplo un DataBufferInt

DataBufferInt actualDBAsDBInt = (DataBufferInt) dbActual ; 
DataBufferInt expectedDBAsDBInt = (DataBufferInt) dbExpected ; 

hacer algunos "chequeos" de igual a igual en los tamaños y los bancos de los DataBuffers, entonces bucle

for (int bank = 0; bank < actualDBAsDBInt.getNumBanks(); bank++) { 
    int[] actual = actualDBAsDBInt.getData(bank); 
    int[] expected = expectedDBAsDBInt.getData(bank); 

    // this line may vary depending on your test framework 
    assertTrue(Arrays.equals(actual, expected)); 
} 

Esto está cerca tan rápido como usted puede conseguir causa que está agarrando una parte de los datos a la vez, no de a uno por vez.

+0

La misma profundidad de bit, disposición, etc. es bastante grande. Ayer contesté una pregunta de chicos, en la que básicamente hacía una comparación de imágenes, y descubrió que una era de ARGB_8888 y otra de RGB_565. Estas son grandes suposiciones. Estás en lo cierto, si esos parámetros son ciertos, este sería el método más rápido :-) – trumpetlicks

+0

Como es una prueba unitaria, parece que su prueba debería tener la misma profundidad de bits, etc. Pero estoy de acuerdo contigo en que En general, eso es un poco una suposición. – user949300

0

Puede escribir esa imagen a través de imageio a través de OutputStream a byte[]. En mi código, se ve más o menos así:

byte[] encodeJpegLossless(BufferedImage img){...using imageio...} 
... 
Assert.assertTrue(Arrays.equals(encodeJpegLossless(img1) 
           ,encodeJpegLossless(img2) 
           ) 
       ); 
1

me cambió function that equals by pixels en Groovy, pueden ser útiles:

boolean imagesAreEqual(BufferedImage image1, BufferedImage image2) { 
    if (image1.width != image2.width || image1.height != image2.height) { 
     return false 
    } 
    for (int x = 1; x < image2.width; x++) { 
     for (int y = 1; y < image2.height; y++) { 
      if (image1.getRGB(x, y) != image2.getRGB(x, y)) { 
       return false 
      } 
     } 
    } 
    return true 
} 
Cuestiones relacionadas