deepfake-detector / ai_detector.py
yaya36095's picture
Upload 8 files
eda917f verified
"""
Enhanced AI Image Detector - Hugging Face Version
This model detects whether an image is real or AI-generated using a trained PyTorch model.
"""
import os
import cv2
import numpy as np
from PIL import Image
import json
from pytorch_model import PyTorchAIDetector
class EnhancedAIDetector:
"""
Enhanced detector for AI-generated images using a trained PyTorch model.
"""
def __init__(self, model_path='best_model_improved.pth'):
"""
Initialize the enhanced detector with a trained PyTorch model.
Args:
model_path: Path to the trained PyTorch model
"""
# Initialize the PyTorch model
self.model = PyTorchAIDetector(model_path)
def analyze_image(self, image_path):
"""
Analyze an image to detect if it's AI-generated using the PyTorch model.
Args:
image_path: Path to the image.
Returns:
Dictionary with analysis results.
"""
# Use the PyTorch model to analyze the image
try:
# Get the model prediction
result = self.model.analyze_image(image_path)
# Enhance the result with additional metadata
result.update({
"model_name": "Enhanced AI Image Detector",
"model_version": "1.0.0",
"confidence": float(result["overall_score"] if result["is_ai_generated"] else 1.0 - result["overall_score"])
})
return result
except Exception as e:
raise ValueError(f"Failed to analyze image: {str(e)}")
def _analyze_noise_patterns(self, cv_img):
"""
Analyze noise patterns in the image.
"""
# Convert to grayscale
gray = cv2.cvtColor(cv_img, cv2.COLOR_BGR2GRAY)
# Apply ELA (Error Level Analysis)
# Save image at 90% quality
temp_path = "temp_ela.jpg"
cv2.imwrite(temp_path, cv_img, [cv2.IMWRITE_JPEG_QUALITY, 90])
# Reload the image
compressed_img = cv2.imread(temp_path)
os.remove(temp_path)
# Calculate difference between original and compressed
if compressed_img is not None:
diff = cv2.absdiff(cv_img, compressed_img)
diff_gray = cv2.cvtColor(diff, cv2.COLOR_BGR2GRAY)
# Calculate difference statistics
mean_diff = np.mean(diff_gray)
std_diff = np.std(diff_gray)
# Normalize result to [0, 1] range
# Higher values indicate higher likelihood of AI-generated
ela_score = min(mean_diff / 8.0, 1.0)
else:
ela_score = 0.5 # Neutral value if ELA analysis fails
# Analyze noise using Laplacian filter
laplacian = cv2.Laplacian(gray, cv2.CV_64F)
laplacian_std = np.std(laplacian)
# Normalize result to [0, 1] range
# Lower values indicate higher likelihood of AI-generated
# (AI-generated images tend to be smoother)
laplacian_score = 1.0 - min(laplacian_std / 15.0, 1.0)
# Analyze noise in frequency domain
# Apply Fourier transform
f_transform = np.fft.fft2(gray)
f_shift = np.fft.fftshift(f_transform)
magnitude_spectrum = 20 * np.log(np.abs(f_shift) + 1)
# Calculate ratio of high to low frequencies
h, w = gray.shape
center_y, center_x = h // 2, w // 2
radius = min(center_y, center_x) // 4
# Create mask for low frequencies
y, x = np.ogrid[:h, :w]
low_freq_mask = ((y - center_y) ** 2 + (x - center_x) ** 2) <= radius ** 2
# Calculate average of low and high frequencies
low_freq_mean = np.mean(magnitude_spectrum[low_freq_mask])
high_freq_mean = np.mean(magnitude_spectrum[~low_freq_mask])
# Calculate ratio
freq_ratio = high_freq_mean / low_freq_mean if low_freq_mean > 0 else 0
# Normalize result to [0, 1] range
# Lower values indicate higher likelihood of AI-generated
freq_score = 1.0 - min(freq_ratio / 0.4, 1.0)
# Combine results with optimized weights
noise_score = 0.4 * ela_score + 0.35 * laplacian_score + 0.25 * freq_score
return noise_score
def _analyze_texture(self, cv_img):
"""
Analyze image texture.
"""
# Convert to grayscale
gray = cv2.cvtColor(cv_img, cv2.COLOR_BGR2GRAY)
# Calculate Gray Level Co-occurrence Matrix (GLCM)
distances = [1]
angles = [0, np.pi/4, np.pi/2, 3*np.pi/4]
# Use skimage to calculate GLCM
try:
glcm = feature.graycomatrix(gray, distances, angles, 256, symmetric=True, normed=True)
# Extract GLCM features
contrast = feature.graycoprops(glcm, 'contrast')[0, 0]
dissimilarity = feature.graycoprops(glcm, 'dissimilarity')[0, 0]
homogeneity = feature.graycoprops(glcm, 'homogeneity')[0, 0]
energy = feature.graycoprops(glcm, 'energy')[0, 0]
correlation = feature.graycoprops(glcm, 'correlation')[0, 0]
# Normalize values
contrast_norm = min(contrast / 80.0, 1.0)
dissimilarity_norm = min(dissimilarity / 8.0, 1.0)
homogeneity_norm = homogeneity
energy_norm = energy
correlation_norm = correlation
# AI-generated images tend to be more homogeneous and less contrasted
texture_score = (
0.15 * (1.0 - contrast_norm) +
0.15 * (1.0 - dissimilarity_norm) +
0.25 * homogeneity_norm +
0.25 * energy_norm +
0.20 * correlation_norm
)
except Exception:
# Use alternative analysis if GLCM fails
# Calculate image variance
variance = np.var(gray)
variance_norm = min(variance / 800.0, 1.0)
# Calculate entropy
hist = cv2.calcHist([gray], [0], None, [256], [0, 256])
hist = hist / hist.sum()
entropy = -np.sum(hist * np.log2(hist + 1e-10))
entropy_norm = min(entropy / 7.5, 1.0)
# AI-generated images tend to have less variance and entropy
texture_score = 0.6 * (1.0 - variance_norm) + 0.4 * (1.0 - entropy_norm)
return texture_score
def _analyze_color_coherence(self, cv_img):
"""
Analyze color coherence in the image.
"""
# Split image into BGR channels
b, g, r = cv2.split(cv_img)
# Calculate standard deviation for each channel
std_b = np.std(b)
std_g = np.std(g)
std_r = np.std(r)
# Calculate average standard deviation
avg_std = (std_b + std_g + std_r) / 3.0
# Normalize result to [0, 1] range
# Lower values indicate higher likelihood of AI-generated
std_score = 1.0 - min(avg_std / 45.0, 1.0)
# Analyze color distribution
hist_b = cv2.calcHist([b], [0], None, [64], [0, 256])
hist_g = cv2.calcHist([g], [0], None, [64], [0, 256])
hist_r = cv2.calcHist([r], [0], None, [64], [0, 256])
# Normalize histograms
hist_b = hist_b / hist_b.sum()
hist_g = hist_g / hist_g.sum()
hist_r = hist_r / hist_r.sum()
# Calculate histogram entropy
entropy_b = -np.sum(hist_b * np.log2(hist_b + 1e-10))
entropy_g = -np.sum(hist_g * np.log2(hist_g + 1e-10))
entropy_r = -np.sum(hist_r * np.log2(hist_r + 1e-10))
# Calculate average entropy
avg_entropy = (entropy_b + entropy_g + entropy_r) / 3.0
# Normalize result to [0, 1] range
# Lower values indicate higher likelihood of AI-generated
entropy_score = 1.0 - min(avg_entropy / 5.8, 1.0)
# Analyze channel relationships
correlation_bg = np.corrcoef(b.flatten(), g.flatten())[0, 1]
correlation_br = np.corrcoef(b.flatten(), r.flatten())[0, 1]
correlation_gr = np.corrcoef(g.flatten(), r.flatten())[0, 1]
# Calculate average correlation
avg_correlation = (abs(correlation_bg) + abs(correlation_br) + abs(correlation_gr)) / 3.0
# Normalize result to [0, 1] range
# Higher values indicate higher likelihood of AI-generated
correlation_score = min(avg_correlation, 1.0)
# Combine results with optimized weights
color_score = 0.25 * std_score + 0.25 * entropy_score + 0.5 * correlation_score
return color_score
def _analyze_edges(self, cv_img):
"""
Analyze edges in the image.
"""
# Convert to grayscale
gray = cv2.cvtColor(cv_img, cv2.COLOR_BGR2GRAY)
# Detect edges using Canny
edges = cv2.Canny(gray, 100, 200)
# Calculate ratio of edge pixels
edge_ratio = np.count_nonzero(edges) / edges.size
# Normalize result to [0, 1] range
# Lower values indicate higher likelihood of AI-generated
edge_ratio_score = 1.0 - min(edge_ratio / 0.08, 1.0)
# Analyze edge consistency
# Apply Sobel filter
sobelx = cv2.Sobel(gray, cv2.CV_64F, 1, 0, ksize=3)
sobely = cv2.Sobel(gray, cv2.CV_64F, 0, 1, ksize=3)
# Calculate gradient direction
gradient_direction = np.arctan2(sobely, sobelx)
# Calculate histogram of gradient directions
hist, _ = np.histogram(gradient_direction, bins=36, range=(-np.pi, np.pi))
hist = hist / hist.sum()
# Calculate histogram entropy
entropy = -np.sum(hist * np.log2(hist + 1e-10))
# Normalize result to [0, 1] range
# Lower values indicate higher likelihood of AI-generated
entropy_score = 1.0 - min(entropy / 5.0, 1.0)
# Additional analysis: edge consistency
# Calculate gradient magnitude
gradient_magnitude = np.sqrt(sobelx**2 + sobely**2)
# Calculate standard deviation of gradient magnitude
gradient_std = np.std(gradient_magnitude)
# Normalize result to [0, 1] range
# Lower values indicate higher likelihood of AI-generated
gradient_score = 1.0 - min(gradient_std / 30.0, 1.0)
# Combine results with optimized weights
edge_score = 0.4 * edge_ratio_score + 0.3 * entropy_score + 0.3 * gradient_score
return edge_score
def _analyze_faces(self, cv_img):
"""
Analyze faces in the image.
"""
# Convert to grayscale
gray = cv2.cvtColor(cv_img, cv2.COLOR_BGR2GRAY)
# Detect faces
faces = self.face_cascade.detectMultiScale(gray, 1.3, 5)
if len(faces) == 0:
return None
face_scores = []
for (x, y, w, h) in faces:
# Extract face region
face_roi = gray[y:y+h, x:x+w]
# Detect eyes
eyes = self.eye_cascade.detectMultiScale(face_roi)
# Analyze face symmetry
symmetry_score = self._analyze_face_symmetry(face_roi)
# Analyze face texture
texture_score = self._analyze_face_texture(face_roi)
# Analyze face proportions
proportion_score = self._analyze_face_proportions(face_roi, eyes)
# Additional analysis: face details
detail_score = self._analyze_face_details(face_roi)
# Combine results with optimized weights
face_score = 0.3 * symmetry_score + 0.25 * texture_score + 0.25 * proportion_score + 0.2 * detail_score
face_scores.append(face_score)
# Calculate average face score
avg_face_score = np.mean(face_scores) if face_scores else 0.5
return avg_face_score
def _analyze_face_symmetry(self, face_roi):
"""
Analyze face symmetry.
"""
# Split face into halves
h, w = face_roi.shape
midpoint = w // 2
left_half = face_roi[:, :midpoint]
right_half = face_roi[:, midpoint:]
# Flip right half
right_half_flipped = cv2.flip(right_half, 1)
# Resize flipped right half to match left half
if left_half.shape[1] != right_half_flipped.shape[1]:
right_half_flipped = cv2.resize(right_half_flipped, (left_half.shape[1], left_half.shape[0]))
# Calculate difference between halves
diff = cv2.absdiff(left_half, right_half_flipped)
# Calculate average difference
mean_diff = np.mean(diff)
# Normalize result to [0, 1] range
# Lower values indicate more symmetry
# AI-generated faces tend to be more symmetrical
symmetry_score = 1.0 - min(mean_diff / 25.0, 1.0)
return symmetry_score
def _analyze_face_texture(self, face_roi):
"""
Analyze face texture.
"""
# Apply Laplacian filter to detect details
laplacian = cv2.Laplacian(face_roi, cv2.CV_64F)
laplacian_std = np.std(laplacian)
# Normalize result to [0, 1] range
# Lower values indicate fewer details
# AI-generated faces tend to have fewer details
texture_score = 1.0 - min(laplacian_std / 15.0, 1.0)
return texture_score
def _analyze_face_proportions(self, face_roi, eyes):
"""
Analyze face proportions.
"""
h, w = face_roi.shape
if len(eyes) >= 2:
# Sort eyes from left to right
eyes = sorted(eyes, key=lambda e: e[0])
# Calculate distance between eyes
eye1_center = (eyes[0][0] + eyes[0][2] // 2, eyes[0][1] + eyes[0][3] // 2)
eye2_center = (eyes[1][0] + eyes[1][2] // 2, eyes[1][1] + eyes[1][3] // 2)
eye_distance = np.sqrt((eye2_center[0] - eye1_center[0]) ** 2 + (eye2_center[1] - eye1_center[1]) ** 2)
# Calculate ratio of eye distance to face width
eye_ratio = eye_distance / w
# Analyze ratio
# In natural faces, eye distance is about 0.3 of face width
proportion_score = 1.0 - min(abs(eye_ratio - 0.3) / 0.2, 1.0)
else:
# If two eyes are not found, use neutral value
proportion_score = 0.5
return proportion_score
def _analyze_face_details(self, face_roi):
"""
Analyze face details.
"""
# Apply Gabor filter to detect fine details
# Create Gabor filter
ksize = 31
sigma = 4.0
theta = 0
lambd = 10.0
gamma = 0.5
# Apply Gabor filter at different angles
angles = [0, np.pi/4, np.pi/2, 3*np.pi/4]
responses = []
for theta in angles:
kernel = cv2.getGaborKernel((ksize, ksize), sigma, theta, lambd, gamma, 0, ktype=cv2.CV_32F)
filtered = cv2.filter2D(face_roi, cv2.CV_8UC3, kernel)
responses.append(filtered)
# Combine responses
combined_response = np.zeros_like(face_roi)
for response in responses:
combined_response = cv2.add(combined_response, response)
# Calculate standard deviation of combined response
response_std = np.std(combined_response)
# Normalize result to [0, 1] range
# Lower values indicate fewer details
# AI-generated faces tend to have fewer details
detail_score = 1.0 - min(response_std / 25.0, 1.0)
return detail_score
def _analyze_additional_features(self, cv_img):
"""
Analyze additional features for borderline images.
"""
# Convert to grayscale
gray = cv2.cvtColor(cv_img, cv2.COLOR_BGR2GRAY)
# Analyze repetition in the image
# Apply Fourier transform
f_transform = np.fft.fft2(gray)
f_shift = np.fft.fftshift(f_transform)
magnitude_spectrum = np.log(np.abs(f_shift) + 1)
# Calculate standard deviation of spectrum
spectrum_std = np.std(magnitude_spectrum)
# Normalize result to [0, 1] range
# Lower values indicate higher likelihood of AI-generated
spectrum_score = 1.0 - min(spectrum_std / 2.0, 1.0)
# Analyze color gradients
# Convert to HSV space
hsv = cv2.cvtColor(cv_img, cv2.COLOR_BGR2HSV)
h, s, v = cv2.split(hsv)
# Calculate histogram of saturation channel
hist_s = cv2.calcHist([s], [0], None, [64], [0, 256])
hist_s = hist_s / hist_s.sum()
# Calculate histogram entropy
entropy_s = -np.sum(hist_s * np.log2(hist_s + 1e-10))
# Normalize result to [0, 1] range
# Lower values indicate higher likelihood of AI-generated
entropy_score = 1.0 - min(entropy_s / 5.0, 1.0)
# Combine results
additional_score = 0.6 * spectrum_score + 0.4 * entropy_score
return additional_score