`I have a python script that works for me... in Ubuntu Linux. Compares all
pngs in a directory (same size) 4 different ways... its a little crazy but it works for me identifying looping in my 4k uhd art videos I make. I explode the mp4s into pngs and then run this to look for matches.
# Function to compare images using VGG16
# Function to compare images using SSIM
# Function to compare images bit-by-bit
# Function to compare images using Histogram
# Check for matches within the + or - 5% tolerance
#!/usr/bin/env python3
import os
import logging
import numpy as np
import cv2
from tensorflow.keras.applications.vgg16 import VGG16, preprocess_input
from tensorflow.keras.preprocessing import image
from tensorflow.keras.models import Model
from sklearn.metrics.pairwise import cosine_similarity
from skimage.metrics import structural_similarity as ssim
# Configure logging
logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(levelname)s - %(message)s')
# Function to extract features using VGG16
def extract_features(img_path):
logging.debug(f"Extracting features from image: {img_path}")
img = image.load_img(img_path, target_size=(224, 224))
img_data = image.img_to_array(img)
img_data = np.expand_dims(img_data, axis=0)
img_data = preprocess_input(img_data)
features = model.predict(img_data)
logging.debug(f"Features extracted from {img_path}")
return features.flatten()
# Function to compare images using VGG16
def compare_vgg16(img1, img2):
logging.debug(f"Comparing images using VGG16: {img1} vs {img2}")
features1 = extract_features(img1)
features2 = extract_features(img2)
similarity = cosine_similarity([features1], [features2])
logging.debug(f"VGG16 similarity score: {similarity[0][0]}")
return similarity[0][0]
# Function to compare images using SSIM
def compare_ssim(img1, img2):
logging.debug(f"Comparing images using SSIM: {img1} vs {img2}")
image1 = cv2.imread(img1, cv2.IMREAD_GRAYSCALE)
image2 = cv2.imread(img2, cv2.IMREAD_GRAYSCALE)
score, _ = ssim(image1, image2, full=True)
logging.debug(f"SSIM score: {score}")
return score
# Function to compare images using Histogram
def compare_histogram(img1, img2):
logging.debug(f"Comparing images using Histogram: {img1} vs {img2}")
image1 = cv2.imread(img1)
image2 = cv2.imread(img2)
hist1 = cv2.calcHist([image1], [0, 1, 2], None, [8, 8, 8], [0, 256, 0, 256, 0, 256])
hist2 = cv2.calcHist([image2], [0, 1, 2], None, [8, 8, 8], [0, 256, 0, 256, 0, 256])
hist1 = cv2.normalize(hist1, hist1).flatten()
hist2 = cv2.normalize(hist2, hist2).flatten()
similarity = cv2.compareHist(hist1, hist2, cv2.HISTCMP_CORREL)
logging.debug(f"Histogram similarity score: {similarity}")
return similarity
# Function to compare images bit-by-bit
def bit_by_bit_comparison(img1, img2, tolerance=0.05):
logging.debug(f"Performing bit-by-bit comparison: {img1} vs {img2}")
image1 = cv2.imread(img1, cv2.IMREAD_GRAYSCALE)
image2 = cv2.imread(img2, cv2.IMREAD_GRAYSCALE)
if image1.shape != image2.shape:
logging.error("Images have different dimensions and cannot be compared bit-by-bit.")
return None
difference = np.abs(image1.astype(np.int16) - image2.astype(np.int16))
threshold = 255 * tolerance
num_diff_pixels = np.sum(difference > threshold)
total_pixels = image1.size
percentage_difference = (num_diff_pixels / total_pixels) * 100
logging.debug(f"Bit-by-bit difference percentage: {percentage_difference:.2f}%")
return percentage_difference
# Function to get the input directory from the user
def get_input_directory():
logging.debug("Requesting input directory path from the user.")
input_dir = input("Please enter the input directory path: ")
while not os.path.isdir(input_dir):
logging.error("The provided directory path is invalid. Please try again.")
input_dir = input("Please enter the input directory path: ")
logging.info(f"Input directory selected: {input_dir}")
return input_dir
# Main Script Execution
if __name__ == "__main__":
# Ask the user to input the directory containing the frames
frames_dir = get_input_directory()
# Get the list of frame files
logging.debug("Retrieving list of frame files...")
frames = sorted([os.path.join(frames_dir, f) for f in os.listdir(frames_dir) if f.endswith('.png')])
# Variables to track matches
ssim_matches = 0
histogram_matches = 0
vgg16_matches = 0
bit_by_bit_matches = 0
# Tolerance value
tolerance = 0.05
# Load the VGG16 model pre-trained on ImageNet
logging.info("Loading VGG16 model pre-trained on ImageNet...")
base_model = VGG16(weights='imagenet')
model = Model(inputs=base_model.input, outputs=base_model.get_layer('fc1').output)
logging.info("VGG16 model loaded successfully.")
# Compare every frame with every other frame in the dataset
for i in range(len(frames)):
for j in range(i + 1, len(frames)):
logging.debug(f"Comparing {frames[i]} with {frames[j]}...")
ssim_score = compare_ssim(frames[i], frames[j])
hist_similarity = compare_histogram(frames[i], frames[j])
vgg16_similarity = compare_vgg16(frames[i], frames[j])
bit_diff = bit_by_bit_comparison(frames[i], frames[j], tolerance=tolerance)
logging.info(f"Comparing {frames[i]} with {frames[j]}:")
logging.info(f" SSIM: {ssim_score:.4f}")
logging.info(f" Histogram Similarity: {hist_similarity:.4f}")
logging.info(f" VGG16 Cosine Similarity: {vgg16_similarity:.4f}")
logging.info(f" Bit-by-Bit Difference: {bit_diff:.2f}%")
# Check for matches within the + or - 5% tolerance
if ssim_score >= 0.95:
logging.info(" * SSIM match within 5% tolerance")
ssim_matches += 1
if hist_similarity >= 0.95:
logging.info(" * Histogram similarity match within 5% tolerance")
histogram_matches += 1
if vgg16_similarity >= 0.95:
logging.info(" * VGG16 cosine similarity match within 5% tolerance")
vgg16_matches += 1
if bit_diff is not None and bit_diff <= 5:
logging.info(" * Bit-by-Bit comparison match within 5% tolerance")
bit_by_bit_matches += 1
# Summary of matches
logging.info("\nSummary of Matches:")
logging.info(f" SSIM Matches within 5% tolerance: {ssim_matches}")
logging.info(f" Histogram Matches within 5% tolerance: {histogram_matches}")
logging.info(f" VGG16 Matches within 5% tolerance: {vgg16_matches}")
logging.info(f" Bit-by-Bit Matches within 5% tolerance: {bit_by_bit_matches}")