warlord123456
Deploy to Hugging Face
7bbed67
Raw
History Blame Contribute Delete
7.99 kB
import cv2
import numpy as np
import os
import uuid
import matplotlib.pyplot as plt
from scipy.signal import convolve2d
from pathlib import Path
def analyze_cfa_artifacts(image_path, save_dir=None, face_results=None, quality_multiplier=1.0):
"""
Detects the presence and consistency of Color Filter Array (CFA)
demosaicing artifacts, which are present in all real digital camera photos
but absent in pure GAN/Diffusion generations.
"""
try:
# Load image
img = cv2.imread(image_path)
if img is None:
return {"cfa_score": 0.5, "error": "Could not read image"}
# Convert to RGB and Gray
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
gray = cv2.cvtColor(img_rgb, cv2.COLOR_RGB2GRAY)
# CFA Demosaicing residual filter (captures Bayer interpolation artifacts)
# This filter isolates high-frequency diagonal differences inherent to CFA
cfa_filter = np.array([
[-0.25, 0.5, -0.25],
[ 0.5, -1.0, 0.5],
[-0.25, 0.5, -0.25]
])
# Apply filter to extract the CFA residual pattern
cfa_residual = convolve2d(gray.astype(float), cfa_filter, mode='same', boundary='symm')
# Calculate local variance (block processing) to find CFA strength
block_size = 8
h, w = cfa_residual.shape
h_blocks = h // block_size
w_blocks = w // block_size
variance_map = np.zeros((h_blocks, w_blocks))
for i in range(h_blocks):
for j in range(w_blocks):
block = cfa_residual[i*block_size:(i+1)*block_size, j*block_size:(j+1)*block_size]
variance_map[i, j] = np.var(block)
# Normalize variance map for visualization
if np.max(variance_map) > 0:
norm_variance = variance_map / np.max(variance_map)
else:
norm_variance = variance_map
# Analyze Face vs Background if face is detected
face_cfa_variance = 0.0
bg_cfa_variance = 0.0
if face_results and face_results.get("face_detected") and "box" in face_results:
x, y, fw, fh = face_results["box"]
# Map box to block coordinates
bx1 = max(0, x // block_size)
by1 = max(0, y // block_size)
bx2 = min(w_blocks, (x + fw) // block_size)
by2 = min(h_blocks, (y + fh) // block_size)
face_region = variance_map[by1:by2, bx1:bx2]
# Background is everything else (we create a mask)
mask = np.ones_like(variance_map, dtype=bool)
mask[by1:by2, bx1:bx2] = False
bg_region = variance_map[mask]
if face_region.size > 0:
face_cfa_variance = np.mean(face_region)
if bg_region.size > 0:
bg_cfa_variance = np.mean(bg_region)
# If the face has significantly less CFA noise, it's likely synthetic.
# If the face has completely different CFA noise than bg, it's likely spliced.
ratio = face_cfa_variance / (bg_cfa_variance + 1e-6)
# Score calculation:
# Normal ratio is around 0.8 - 1.2.
if ratio < 0.5:
cfa_score = 1.0 - (ratio / 0.5) # 0.0 ratio = 1.0 score
elif ratio > 2.0:
cfa_score = min(1.0, (ratio - 2.0) / 2.0)
else:
cfa_score = abs(1.0 - ratio) * 0.5 # Small penalty for normal variance
# NEW: If the ENTIRE image lacks CFA noise, it's heavily compressed or fully AI-generated!
global_variance = np.mean(variance_map)
# If both regions have very low variance, it's heavily compressed video.
# The ratio becomes mathematically unstable and meaningless.
if face_cfa_variance < 15.0 and bg_cfa_variance < 15.0:
# Bypass ratio penalty for compressed videos, just use global smoothness
cfa_score = max(0.0, min(1.0, (5 - global_variance) / 5)) * 0.4 # Cap confidence
else:
if global_variance < 10.0:
# Override the ratio score if the whole image is smooth
cfa_score = max(cfa_score, min(1.0, (15 - global_variance) / 15))
else:
# No face detected. Measure global CFA strength.
global_variance = np.mean(variance_map)
cfa_score = max(0.0, min(1.0, (5 - global_variance) / 5))
# Scale score by quality (low quality = lower confidence)
cfa_score = cfa_score * quality_multiplier
cfa_score = float(np.clip(cfa_score, 0.05, 0.95))
# --- Visualization Generation ---
plt.style.use('dark_background')
fig, ax = plt.subplots(figsize=(8, 6))
# Display the normalized variance map as a heatmap
# Resize to original dimensions for overlay or display
heatmap_resized = cv2.resize(norm_variance, (w, h), interpolation=cv2.INTER_NEAREST)
# Create a viridis colormap representation
im = ax.imshow(heatmap_resized, cmap='inferno')
# Draw face bounding box if available
if face_results and face_results.get("face_detected") and "box" in face_results:
x, y, fw, fh = face_results["box"]
import matplotlib.patches as patches
rect = patches.Rectangle((x, y), fw, fh, linewidth=2, edgecolor='cyan', facecolor='none', linestyle='dashed')
ax.add_patch(rect)
ax.axis('off')
plt.tight_layout(pad=0)
# Save visualization
filename = f"cfa_{uuid.uuid4().hex[:8]}.png"
if save_dir is None:
save_dir = os.path.join(os.path.dirname(os.path.dirname(__file__)), "static", "results")
os.makedirs(save_dir, exist_ok=True)
save_path = os.path.join(save_dir, filename)
plt.savefig(save_path, bbox_inches='tight', pad_inches=0, dpi=100, facecolor='black')
plt.close(fig)
# Calculate web relative path
if "uploads" in str(save_path):
web_path = "uploads/" + Path(save_path).parts[-2] + "/" + filename
else:
web_path = f"static/results/{filename}"
face_var = float(face_cfa_variance) if 'face_cfa_variance' in locals() else 0.0
bg_var = float(bg_cfa_variance) if 'bg_cfa_variance' in locals() else 0.0
score = float(np.clip(cfa_score, 0.05, 0.95))
return {
"cfa_score": score,
"face_variance": face_var,
"bg_variance": bg_var,
"cfa_map_path": web_path,
"explanation": {
"what_happened": "Extracted the microscopic Color Filter Array (Bayer) grid pattern created by physical camera sensors.",
"result": "Grid Disrupted (Deepfake)" if score > 0.5 else "Authentic Sensor Grid",
"why_it_happened": "The face region's microscopic pixel grid was completely destroyed or out-of-sync compared to the background, which happens when AI generates new pixels." if score > 0.5 else "The physical camera pixel grid is perfectly consistent across the entire image.",
"variables": {
"Face Grid Variance": f"{face_var:.4f}",
"Background Grid Variance": f"{bg_var:.4f}",
"Mismatch Ratio": f"{(bg_var / max(0.0001, face_var)):.2f}x"
}
}
}
except Exception as e:
print(f"Error in CFA analysis: {e}")
return {"cfa_score": 0.5, "error": str(e)}
if __name__ == "__main__":
import sys
if len(sys.argv) > 1:
res = analyze_cfa_artifacts(sys.argv[1])
print(res)