| """ |
| 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 |
| """ |
| |
| 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. |
| """ |
| |
| try: |
| |
| result = self.model.analyze_image(image_path) |
| |
| |
| 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. |
| """ |
| |
| gray = cv2.cvtColor(cv_img, cv2.COLOR_BGR2GRAY) |
| |
| |
| |
| temp_path = "temp_ela.jpg" |
| cv2.imwrite(temp_path, cv_img, [cv2.IMWRITE_JPEG_QUALITY, 90]) |
| |
| |
| compressed_img = cv2.imread(temp_path) |
| os.remove(temp_path) |
| |
| |
| if compressed_img is not None: |
| diff = cv2.absdiff(cv_img, compressed_img) |
| diff_gray = cv2.cvtColor(diff, cv2.COLOR_BGR2GRAY) |
| |
| |
| mean_diff = np.mean(diff_gray) |
| std_diff = np.std(diff_gray) |
| |
| |
| |
| ela_score = min(mean_diff / 8.0, 1.0) |
| else: |
| ela_score = 0.5 |
| |
| |
| laplacian = cv2.Laplacian(gray, cv2.CV_64F) |
| laplacian_std = np.std(laplacian) |
| |
| |
| |
| |
| laplacian_score = 1.0 - min(laplacian_std / 15.0, 1.0) |
| |
| |
| |
| f_transform = np.fft.fft2(gray) |
| f_shift = np.fft.fftshift(f_transform) |
| magnitude_spectrum = 20 * np.log(np.abs(f_shift) + 1) |
| |
| |
| h, w = gray.shape |
| center_y, center_x = h // 2, w // 2 |
| radius = min(center_y, center_x) // 4 |
| |
| |
| y, x = np.ogrid[:h, :w] |
| low_freq_mask = ((y - center_y) ** 2 + (x - center_x) ** 2) <= radius ** 2 |
| |
| |
| low_freq_mean = np.mean(magnitude_spectrum[low_freq_mask]) |
| high_freq_mean = np.mean(magnitude_spectrum[~low_freq_mask]) |
| |
| |
| freq_ratio = high_freq_mean / low_freq_mean if low_freq_mean > 0 else 0 |
| |
| |
| |
| freq_score = 1.0 - min(freq_ratio / 0.4, 1.0) |
| |
| |
| 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. |
| """ |
| |
| gray = cv2.cvtColor(cv_img, cv2.COLOR_BGR2GRAY) |
| |
| |
| distances = [1] |
| angles = [0, np.pi/4, np.pi/2, 3*np.pi/4] |
| |
| |
| try: |
| glcm = feature.graycomatrix(gray, distances, angles, 256, symmetric=True, normed=True) |
| |
| |
| 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] |
| |
| |
| 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 |
| |
| |
| 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: |
| |
| |
| variance = np.var(gray) |
| variance_norm = min(variance / 800.0, 1.0) |
| |
| |
| 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) |
| |
| |
| 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. |
| """ |
| |
| b, g, r = cv2.split(cv_img) |
| |
| |
| std_b = np.std(b) |
| std_g = np.std(g) |
| std_r = np.std(r) |
| |
| |
| avg_std = (std_b + std_g + std_r) / 3.0 |
| |
| |
| |
| std_score = 1.0 - min(avg_std / 45.0, 1.0) |
| |
| |
| 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]) |
| |
| |
| hist_b = hist_b / hist_b.sum() |
| hist_g = hist_g / hist_g.sum() |
| hist_r = hist_r / hist_r.sum() |
| |
| |
| 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)) |
| |
| |
| avg_entropy = (entropy_b + entropy_g + entropy_r) / 3.0 |
| |
| |
| |
| entropy_score = 1.0 - min(avg_entropy / 5.8, 1.0) |
| |
| |
| 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] |
| |
| |
| avg_correlation = (abs(correlation_bg) + abs(correlation_br) + abs(correlation_gr)) / 3.0 |
| |
| |
| |
| correlation_score = min(avg_correlation, 1.0) |
| |
| |
| 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. |
| """ |
| |
| gray = cv2.cvtColor(cv_img, cv2.COLOR_BGR2GRAY) |
| |
| |
| edges = cv2.Canny(gray, 100, 200) |
| |
| |
| edge_ratio = np.count_nonzero(edges) / edges.size |
| |
| |
| |
| edge_ratio_score = 1.0 - min(edge_ratio / 0.08, 1.0) |
| |
| |
| |
| sobelx = cv2.Sobel(gray, cv2.CV_64F, 1, 0, ksize=3) |
| sobely = cv2.Sobel(gray, cv2.CV_64F, 0, 1, ksize=3) |
| |
| |
| gradient_direction = np.arctan2(sobely, sobelx) |
| |
| |
| hist, _ = np.histogram(gradient_direction, bins=36, range=(-np.pi, np.pi)) |
| hist = hist / hist.sum() |
| |
| |
| entropy = -np.sum(hist * np.log2(hist + 1e-10)) |
| |
| |
| |
| entropy_score = 1.0 - min(entropy / 5.0, 1.0) |
| |
| |
| |
| gradient_magnitude = np.sqrt(sobelx**2 + sobely**2) |
| |
| |
| gradient_std = np.std(gradient_magnitude) |
| |
| |
| |
| gradient_score = 1.0 - min(gradient_std / 30.0, 1.0) |
| |
| |
| 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. |
| """ |
| |
| gray = cv2.cvtColor(cv_img, cv2.COLOR_BGR2GRAY) |
| |
| |
| faces = self.face_cascade.detectMultiScale(gray, 1.3, 5) |
| |
| if len(faces) == 0: |
| return None |
| |
| face_scores = [] |
| |
| for (x, y, w, h) in faces: |
| |
| face_roi = gray[y:y+h, x:x+w] |
| |
| |
| eyes = self.eye_cascade.detectMultiScale(face_roi) |
| |
| |
| symmetry_score = self._analyze_face_symmetry(face_roi) |
| |
| |
| texture_score = self._analyze_face_texture(face_roi) |
| |
| |
| proportion_score = self._analyze_face_proportions(face_roi, eyes) |
| |
| |
| detail_score = self._analyze_face_details(face_roi) |
| |
| |
| face_score = 0.3 * symmetry_score + 0.25 * texture_score + 0.25 * proportion_score + 0.2 * detail_score |
| face_scores.append(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. |
| """ |
| |
| h, w = face_roi.shape |
| midpoint = w // 2 |
| |
| left_half = face_roi[:, :midpoint] |
| right_half = face_roi[:, midpoint:] |
| |
| |
| right_half_flipped = cv2.flip(right_half, 1) |
| |
| |
| 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])) |
| |
| |
| diff = cv2.absdiff(left_half, right_half_flipped) |
| |
| |
| mean_diff = np.mean(diff) |
| |
| |
| |
| |
| symmetry_score = 1.0 - min(mean_diff / 25.0, 1.0) |
| |
| return symmetry_score |
| |
| def _analyze_face_texture(self, face_roi): |
| """ |
| Analyze face texture. |
| """ |
| |
| laplacian = cv2.Laplacian(face_roi, cv2.CV_64F) |
| laplacian_std = np.std(laplacian) |
| |
| |
| |
| |
| 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: |
| |
| eyes = sorted(eyes, key=lambda e: e[0]) |
| |
| |
| 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) |
| |
| |
| eye_ratio = eye_distance / w |
| |
| |
| |
| proportion_score = 1.0 - min(abs(eye_ratio - 0.3) / 0.2, 1.0) |
| else: |
| |
| proportion_score = 0.5 |
| |
| return proportion_score |
| |
| def _analyze_face_details(self, face_roi): |
| """ |
| Analyze face details. |
| """ |
| |
| |
| ksize = 31 |
| sigma = 4.0 |
| theta = 0 |
| lambd = 10.0 |
| gamma = 0.5 |
| |
| |
| 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) |
| |
| |
| combined_response = np.zeros_like(face_roi) |
| for response in responses: |
| combined_response = cv2.add(combined_response, response) |
| |
| |
| response_std = np.std(combined_response) |
| |
| |
| |
| |
| 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. |
| """ |
| |
| gray = cv2.cvtColor(cv_img, cv2.COLOR_BGR2GRAY) |
| |
| |
| |
| f_transform = np.fft.fft2(gray) |
| f_shift = np.fft.fftshift(f_transform) |
| magnitude_spectrum = np.log(np.abs(f_shift) + 1) |
| |
| |
| spectrum_std = np.std(magnitude_spectrum) |
| |
| |
| |
| spectrum_score = 1.0 - min(spectrum_std / 2.0, 1.0) |
| |
| |
| |
| hsv = cv2.cvtColor(cv_img, cv2.COLOR_BGR2HSV) |
| h, s, v = cv2.split(hsv) |
| |
| |
| hist_s = cv2.calcHist([s], [0], None, [64], [0, 256]) |
| hist_s = hist_s / hist_s.sum() |
| |
| |
| entropy_s = -np.sum(hist_s * np.log2(hist_s + 1e-10)) |
| |
| |
| |
| entropy_score = 1.0 - min(entropy_s / 5.0, 1.0) |
| |
| |
| additional_score = 0.6 * spectrum_score + 0.4 * entropy_score |
| |
| return additional_score |
|
|