Fotos Duplikate finden

M

MiMa

Top Contributor
Hallo,
ich hoffe das ist hier der richtige Bereich!
Seit den digitalen Kameras werden die Ordner mit Bildern überflutet.
Einige Duplikate werden erkannt durch gleiche Namen + Dateigröße + Datum.
Oftmals ist dies nur die halbe Miete, exakt identische Fotos werden durch den Inhalt und MD5 verfahren ebenfalls erfolgreich gefunden.
Aber auch hier bleiben oft immer noch genügend Duplikate unentdeckt, weil das gleiche Bild in verschiedenen Größen vorliegt.
Zum Beispiel Aperture und andere Programme erstellen meist Vorschauansichten und beim Migrieren hat man eine unglaubliche Datenflut zu bearbeiten.
Bei mir sind es aktuell 4.75 Millionen Dateien die im Katalog gelistet werden. :(
Das sichtbare Bild ist zwar gleich aber auf die Daten des Inhaltes, Prüfsumme, Datum und andere Daten helfen hier nicht weiter.

Weis jemand ob es ein Programm gibt welches genau dieses Problem löst.
Oder kann man mit Java eine optische Bildprüfung programmieren um solche Duplikate auf zu spüren?

Danke
Mi
 
X

Xyz1

Gast
Hallo , hier mein Ansatz:
Java:
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;

import javax.imageio.ImageIO;

public class ICompare {
    public static boolean equals(String fn1, String fn2, float threshold) throws IOException {
        BufferedImage i1 = ImageIO.read(new File(fn1));
        BufferedImage i2 = ImageIO.read(new File(fn2));

        Image tmp = i1.getScaledInstance(100, 100, Image.SCALE_FAST);
        BufferedImage i3 = new BufferedImage(100, 100, BufferedImage.TYPE_INT_ARGB);
        Graphics2D g2d = i3.createGraphics();
        g2d.drawImage(tmp, 0, 0, null);
        g2d.dispose();

        tmp = i2.getScaledInstance(100, 100, Image.SCALE_FAST);
        BufferedImage i4 = new BufferedImage(100, 100, BufferedImage.TYPE_INT_ARGB);
        g2d = i4.createGraphics();
        g2d.drawImage(tmp, 0, 0, null);
        g2d.dispose();

        int wrongs = 0;
        for (int y = 0; y < 100; y++) {
            for (int x = 0; x < 100; x++) {
                int rgb1 = i3.getRGB(x, y);
                int gray1 = (((rgb1 >> 16) & 0xFF) + ((rgb1 >> 8) & 0xFF) + ((rgb1 & 0xFF))) / 3;
                int rgb2 = i4.getRGB(x, y);
                int gray2 = (((rgb2 >> 16) & 0xFF) + ((rgb2 >> 8) & 0xFF) + ((rgb2 & 0xFF))) / 3;
                if (Math.abs(gray1 - gray2) >= 10) {
                    wrongs++;
                }
            }
        }
        System.out.println(1.0 - wrongs / 10000.0);
        return 1.0 - wrongs / 10000.0 >= threshold;
    }

    public static void main(String[] args) throws IOException {
        System.out.println(equals("C:\\Users\\\\Desktop\\th2.jpg", "C:\\Users\\\\Desktop\\th2.jpg", 0.5f));
        System.out.println(equals("C:\\Users\\\\Desktop\\th1.jpg", "C:\\Users\\\\Desktop\\th2.jpg", 0.5f));
        System.out.println(equals("C:\\Users\\\\Desktop\\th1.jpg", "C:\\Users\\\\Desktop\\th3.jpg", 0.5f));
        System.out.println(equals("C:\\Users\\\\Desktop\\th2.jpg", "C:\\Users\\\\Desktop\\th3.jpg", 0.5f));
    }
}

Folgende Testbilder:
th1
th1.jpg


th2
th2.jpg


th3
th3.jpg


Ausgabe:
Code:
1.0
true
0.5502
true
0.12270000000000003
false
0.11019999999999996
false
 
X

Xyz1

Gast
Mir fällt gerade auf, dass das mit einem Ordner mit nur 20 Bildern schon recht lange braucht:
Java:
		// ...
		ArrayList<Object[]> res = new ArrayList<Object[]>();
		for (int i = 0; i < paths.size(); i++) {
			for (int j = i + 1; j < paths.size(); j++) {
				float t = equals(paths.get(i).toString(), paths.get(j).toString(), 0.6f);
				if (t >= 0.6f) {
					res.add(new Object[] { paths.get(i).toString(), paths.get(j).toString(), t });
				}
			}
		}
		Collections.sort(res, new Comparator<Object[]>() {
			@Override
			public int compare(Object[] o1, Object[] o2) {
				int i1 = Float.compare((float) o2[2], (float) o1[2]);
				return i1;
			}
		});
		for (Object[] objects : res) {
			System.out.printf("%40s %40s %10s %n", objects);
		}


Das ist der Nachteil bei Bildervergleichen ;)
 
M

MiMa

Top Contributor
Vielen Dank für die Code-Vorschläge.
Aber wie arbeitet der Algorithmus?
Wenn ich mir ein Bild anschaue und dann das nächst, weis ich das die anders aussehen.
Aber der PC kann das so nicht, wie wird denn hier vor gegangen?
Mit Grafik habe ich in Java bisher noch nicht gearbeitet und kenne daher einige Funktionen oder Befehle nicht.
Ein Duplikat sollte erkannt werden wenn das gleiche Bild sagen wir 1200x800 pixel ist und das gleiche Bild in 600x400 pixel groß ist (Originalbild, Vorschaubild).

Ich kann mich morgen erst darum kümmern und würde mal fragen ob der Code das Duplikat erkennt, wenn die Testbilder in unterschiedlicher Auflösung als Duplikat erkannt wird?

Danke
Mi
 
mihe7

mihe7

Top Contributor
Wenn du jedes Bild mit jedem anderen vergleichen willst, sind das sehr sehr viele Vergleiche. Da hilft auch keine "Beschleunigung" des Programms.
Wenn die Bilder tatsächlich optisch identisch sind, sollte das in O(n log n) möglich sein.

Die Skalierung auf 8x8-Pixel, dann Helligkeitswerte mit Threshold filtern (hell/dunkel), so erhält man einen 64-Bit-Hash. Sortieren, durchlaufen, fertig :)
 
X

Xyz1

Gast
Java:
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.nio.file.FileVisitResult;
import java.nio.file.FileVisitor;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import javax.imageio.ImageIO;
import javax.swing.JFileChooser;

public class ICompare {
	public static int[][] getGrayscale(String fn) throws IOException {
		int[][] r = new int[8][8];

		BufferedImage i1 = ImageIO.read(new File(fn));
		Image tmp = i1.getScaledInstance(8, 8, Image.SCALE_FAST);
		BufferedImage i3 = new BufferedImage(8, 8, BufferedImage.TYPE_INT_ARGB);
		Graphics2D g2d = i3.createGraphics();
		g2d.drawImage(tmp, 0, 0, null);
		g2d.dispose();

		for (int y = 0; y < 8; y++) {
			for (int x = 0; x < 8; x++) {
				int rgb1 = i3.getRGB(x, y);
				int gray1 = (((rgb1 >> 16) & 0xFF) + ((rgb1 >> 8) & 0xFF) + ((rgb1 & 0xFF))) / 3;
				r[y][x] = gray1;
			}
		}
		return r;
	}

	public static float equals(int[][] a, int[][] b, float threshold) {
		int c = 0;
		for (int y = 0; y < 8; y++) {
			for (int x = 0; x < 8; x++) {
				if (Math.abs(a[y][x] - b[y][x]) >= 10) {
					c++;
				}
			}
		}
		System.out.println((1.0f - c / 64.0f >= threshold) + " " + (1.0f - c / 64.0f));
		return 1.0f - c / 64.0f;
	}

	public static void main(String[] args) throws IOException {
		final float threshold = 0.45f;
		ArrayList<Path> paths = new ArrayList<Path>();
		JFileChooser jfc = new JFileChooser();
		jfc.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
		jfc.showOpenDialog(null);
		Files.walkFileTree(jfc.getSelectedFile().toPath(), new FileVisitor<Path>() {
			@Override
			public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
				return FileVisitResult.CONTINUE;
			}

			@Override
			public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
				if (file.toString().toLowerCase().endsWith(".jpg") || file.toString().toLowerCase().endsWith(".jpeg")) {
					paths.add(file);
				}
				return FileVisitResult.CONTINUE;
			}

			@Override
			public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException {
				return FileVisitResult.CONTINUE;
			}

			@Override
			public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
				return FileVisitResult.CONTINUE;
			}
		});
		ArrayList<int[][]> hashes = new ArrayList<int[][]>();
		for (Path path : paths) {
			hashes.add(getGrayscale(path.toString()));
		}
		ArrayList<Object[]> res = new ArrayList<Object[]>();
		for (int i = 0; i < paths.size(); i++) {
			for (int j = i + 1; j < paths.size(); j++) {
				float t = equals(hashes.get(i), hashes.get(j), threshold);
				if (t >= threshold) {
					res.add(new Object[] { paths.get(i).toString(), paths.get(j).toString(), t });
				}
			}
		}
		Collections.sort(res, new Comparator<Object[]>() {
			@Override
			public int compare(Object[] o1, Object[] o2) {
				int i1 = Float.compare((float) o2[2], (float) o1[2]);
				return i1;
			}
		});
		for (Object[] objects : res) {
			System.out.printf("%40s %40s %10s %n", objects);
		}
	}
}
 
M

MiMa

Top Contributor
Oh das mit den 4 Mio. Bildern hatte ich wohl überlesen :D
Ja das ist übel. Besonders Apple Aperture hat freudig neben den Master Dateien noch diverse Vorschaudateien und anderes erzeugt. Somit wurde alles aufgebläht. Datensicherungen auf externen NAS-Systemen, externe Festplatten als Archive, dann kann sowas schon mal passieren.
Jetzt wo ich keinen Mac mehr habe muss ich alles unter Windows neu strukturieren und habe mich entschieden alles auf Dateibasis in Ordner ab zu legen und mit einem Katalogprogramm das Management zu übernehmen. So ist es Platformunabhängig und da wird nichts aufgebläht.
Also Frühjahrsputz in den Fotodateien. :cool:
Fotos.JPG


Zum Algorithmus würde ich gerne mehr erfahren.
Werden die Bilder Pixel für Pixel miteinander verglichen?
Grauwerte, Helligkeiten, 8x8 pixel vergleichen?
Bitte mal kurz erklären.
Danke
Mi
 
Zuletzt bearbeitet:
M

MiMa

Top Contributor
Java:
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.nio.file.FileVisitResult;
import java.nio.file.FileVisitor;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import javax.imageio.ImageIO;
import javax.swing.JFileChooser;

public class ICompare {
    public static int[][] getGrayscale(String fn) throws IOException {
        int[][] r = new int[8][8];

        BufferedImage i1 = ImageIO.read(new File(fn));
        Image tmp = i1.getScaledInstance(8, 8, Image.SCALE_FAST);
        BufferedImage i3 = new BufferedImage(8, 8, BufferedImage.TYPE_INT_ARGB);
        Graphics2D g2d = i3.createGraphics();
        g2d.drawImage(tmp, 0, 0, null);
        g2d.dispose();

        for (int y = 0; y < 8; y++) {
            for (int x = 0; x < 8; x++) {
                int rgb1 = i3.getRGB(x, y);
                int gray1 = (((rgb1 >> 16) & 0xFF) + ((rgb1 >> 8) & 0xFF) + ((rgb1 & 0xFF))) / 3;
                r[y][x] = gray1;
            }
        }
        return r;
    }

    public static float equals(int[][] a, int[][] b, float threshold) {
        int c = 0;
        for (int y = 0; y < 8; y++) {
            for (int x = 0; x < 8; x++) {
                if (Math.abs(a[y][x] - b[y][x]) >= 10) {
                    c++;
                }
            }
        }
        System.out.println((1.0f - c / 64.0f >= threshold) + " " + (1.0f - c / 64.0f));
        return 1.0f - c / 64.0f;
    }

    public static void main(String[] args) throws IOException {
        final float threshold = 0.45f;
        ArrayList<Path> paths = new ArrayList<Path>();
        JFileChooser jfc = new JFileChooser();
        jfc.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
        jfc.showOpenDialog(null);
        Files.walkFileTree(jfc.getSelectedFile().toPath(), new FileVisitor<Path>() {
            @Override
            public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
                return FileVisitResult.CONTINUE;
            }

            @Override
            public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
                if (file.toString().toLowerCase().endsWith(".jpg") || file.toString().toLowerCase().endsWith(".jpeg")) {
                    paths.add(file);
                }
                return FileVisitResult.CONTINUE;
            }

            @Override
            public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException {
                return FileVisitResult.CONTINUE;
            }

            @Override
            public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
                return FileVisitResult.CONTINUE;
            }
        });
        ArrayList<int[][]> hashes = new ArrayList<int[][]>();
        for (Path path : paths) {
            hashes.add(getGrayscale(path.toString()));
        }
        ArrayList<Object[]> res = new ArrayList<Object[]>();
        for (int i = 0; i < paths.size(); i++) {
            for (int j = i + 1; j < paths.size(); j++) {
                float t = equals(hashes.get(i), hashes.get(j), threshold);
                if (t >= threshold) {
                    res.add(new Object[] { paths.get(i).toString(), paths.get(j).toString(), t });
                }
            }
        }
        Collections.sort(res, new Comparator<Object[]>() {
            @Override
            public int compare(Object[] o1, Object[] o2) {
                int i1 = Float.compare((float) o2[2], (float) o1[2]);
                return i1;
            }
        });
        for (Object[] objects : res) {
            System.out.printf("%40s %40s %10s %n", objects);
        }
    }
}
Habe mit diesem Code mal 34 Dateien geprüft und "total time: 16 seconds"
 
M

MiMa

Top Contributor
Funktioniert das auch mit anderen Bildformaten wie .TIF, .png, Canon Raw .cr2 ??
Hab hier mal was zu Algorithmen gefunden Bildvergleich Algorithmen damit ich ein bessere Verständnis davon bekomme.
 
Zuletzt bearbeitet:
X

Xyz1

Gast
Image I/O has built-in support for GIF, PNG, JPEG, BMP, and WBMP. Image I/O is also extensible so that developers or administrators can "plug-in" support for additional formats. For example, plug-ins for TIFF and JPEG 2000 are separately available.
;)
 
M

MiMa

Top Contributor
Danke für den Link der App sieht gut und hilft bestimmt. Das werde ich mir heute auch mal näher ansehen.
Aber gut zu wissen das man das in Java auch machen kann und werde mich auch mit der Programmierung auseinander setzen.
Alles was helfen könnte sehe ich mir an Priorität ist es Duplikate zu entfernen.:)
 
Anzeige

Neue Themen


Oben