| """Standalone comparison of the TWO project models β no changes to either project. |
| |
| main project : M3_best.keras (19 layers, internal Rescaling + ResNet |
| preprocess_input -> expects RAW [0,255]; trained on MULTIPLY-ELA) |
| usama project : M3_best_v2.h5 (191 layers, no internal norm -> expects /255; |
| trained on BRIGHTNESS-ELA) |
| |
| The CASIA test split is held identical for both (canonical SEED=42, 70/15/15, |
| same logic as threshold_analysis.py). Each model is fed the input RANGE it |
| requires. Two views are printed: |
| |
| TABLE A β each project AS-DEPLOYED (its own ELA + its own RGB pipeline) |
| TABLE B β same corrected (brightness) ELA + RGB for both, each in native range |
| (isolates weights/training as far as possible; note the keras model |
| was trained on multiply-ELA, so brightness-ELA is mildly OOD for it) |
| """ |
| import io, os, sys, warnings |
| warnings.filterwarnings("ignore") |
| os.environ["TF_ENABLE_ONEDNN_OPTS"] = "0" |
| os.environ["TF_CPP_MIN_LOG_LEVEL"] = "2" |
| try: |
| sys.stdout.reconfigure(encoding="utf-8") |
| except Exception: |
| pass |
| import numpy as np |
| import tensorflow as tf |
| from PIL import Image, ImageChops, ImageEnhance |
| from sklearn.model_selection import train_test_split |
| from sklearn.metrics import (roc_auc_score, accuracy_score, precision_score, |
| recall_score, f1_score, confusion_matrix, |
| balanced_accuracy_score) |
|
|
| IMG_SIZE = (224, 224) |
| ELA_QUALITY, ELA_SCALE, SEED = 90, 15, 42 |
| N_SAMPLE = 400 |
| CHUNK = 40 |
| BATCH = 8 |
| CASIA = r"G:/My Drive/CASIA2" |
| AU_DIR, TP_DIR = f"{CASIA}/Au", f"{CASIA}/Tp" |
| ELA_AU, ELA_TP = f"{CASIA}_ELA/Au", f"{CASIA}_ELA/Tp" |
| VALID = {'.jpg', '.jpeg', '.png', '.tif', '.tiff', '.bmp'} |
|
|
| KERAS_PATH = "M3_best.keras" |
| H5_PATH = (".cache/models--usamaalam--image-forgery-detection-model/" |
| "snapshots/36b0e93421cee96841eeac2b199afbdf61110c33/M3_best_v2.h5") |
|
|
|
|
| |
| def ela_multiply_arr(pil): |
| """main's ELA: ImageChops.multiply, PIL default resize, RAW [0,255].""" |
| o = pil.convert('RGB') |
| b = io.BytesIO(); o.save(b, 'JPEG', quality=ELA_QUALITY); b.seek(0) |
| comp = Image.open(b) |
| ela = ImageChops.difference(o, comp) |
| ela = ImageChops.multiply(ela, Image.new('RGB', ela.size, |
| (ELA_SCALE, ELA_SCALE, ELA_SCALE))) |
| return np.array(ela.resize(IMG_SIZE)).astype(np.float32) |
|
|
|
|
| def ela_bright_arr(pil): |
| """usama's corrected ELA: brightness-enhance -> JPEG bytes -> tf.image decode |
| + bilinear resize, returned in [0,255] (caller divides by 255 if needed).""" |
| o = pil.convert('RGB') |
| b = io.BytesIO(); o.save(b, 'JPEG', quality=ELA_QUALITY); b.seek(0) |
| rc = Image.open(b).convert('RGB') |
| ela = ImageEnhance.Brightness(ImageChops.difference(o, rc)).enhance(ELA_SCALE) |
| out = io.BytesIO(); ela.save(out, 'JPEG') |
| img = tf.image.decode_jpeg(out.getvalue(), channels=3) |
| img = tf.image.resize(img, IMG_SIZE) |
| return img.numpy().astype(np.float32) |
|
|
|
|
| def rgb_default_arr(pil): |
| """main's RGB: PIL default resize, RAW [0,255].""" |
| return np.array(pil.convert('RGB').resize(IMG_SIZE)).astype(np.float32) |
|
|
|
|
| def rgb_lanczos_arr(pil): |
| """usama's RGB: PIL LANCZOS resize, [0,255] (caller divides by 255).""" |
| return np.array(pil.convert('RGB').resize(IMG_SIZE, Image.LANCZOS)).astype(np.float32) |
|
|
|
|
| |
| def build_paths(): |
| paths, labels = [], [] |
| for d, lab, ela_dir in [(AU_DIR, 0, ELA_AU), (TP_DIR, 1, ELA_TP)]: |
| for f in os.listdir(d): |
| if os.path.splitext(f)[1].lower() in VALID: |
| if os.path.exists(os.path.join(ela_dir, os.path.splitext(f)[0] + '.jpg')): |
| paths.append(os.path.join(d, f)); labels.append(lab) |
| return np.array(paths), np.array(labels) |
|
|
|
|
| def metrics_row(y_true, scores, thr=0.5): |
| yp = (scores > thr).astype(int) |
| return { |
| 'AUC': roc_auc_score(y_true, scores), |
| 'Acc': accuracy_score(y_true, yp), |
| 'BalAcc': balanced_accuracy_score(y_true, yp), |
| 'Prec': precision_score(y_true, yp, zero_division=0), |
| 'Rec': recall_score(y_true, yp, zero_division=0), |
| 'F1': f1_score(y_true, yp, zero_division=0), |
| 'CM': confusion_matrix(y_true, yp).tolist(), |
| } |
|
|
|
|
| def print_table(title, note, res_main, res_usama): |
| print("\n" + "=" * 74) |
| print(title) |
| if note: |
| print(note) |
| print("=" * 74) |
| cols = ['AUC', 'Acc', 'BalAcc', 'Prec', 'Rec', 'F1'] |
| print(f"{'metric':<10}{'main (keras)':>16}{'usama (h5)':>16}{'diff (usama-main)':>20}") |
| print("-" * 74) |
| for c in cols: |
| a, b = res_main[c], res_usama[c] |
| print(f"{c:<10}{a:>16.4f}{b:>16.4f}{b-a:>+20.4f}") |
| print("-" * 74) |
| print(f"confusion matrix [rows=true Au/Tp, cols=pred Au/Tp], threshold 0.5") |
| print(f" main : {res_main['CM']}") |
| print(f" usama: {res_usama['CM']}") |
|
|
|
|
| def main(): |
| print("Loading models...") |
| km = tf.keras.models.load_model(KERAS_PATH, compile=False) |
| hm = tf.keras.models.load_model(H5_PATH, compile=False) |
|
|
| print("Building canonical CASIA test split (SEED=42, 70/15/15)...") |
| paths, labels = build_paths() |
| tr, tmp = train_test_split(np.arange(len(labels)), test_size=0.30, |
| stratify=labels, random_state=SEED) |
| _, test = train_test_split(tmp, test_size=0.50, |
| stratify=labels[tmp], random_state=SEED) |
| rng = np.random.default_rng(0) |
| sub = rng.choice(test, size=min(N_SAMPLE, len(test)), replace=False) |
| sub_paths, y = paths[sub], labels[sub].astype(int) |
| n_au, n_tp = int((y == 0).sum()), int((y == 1).sum()) |
| print(f" test pool={len(test)} evaluating N={len(sub)} (authentic={n_au}, forged={n_tp})") |
|
|
| |
| |
| print(f"Running inference in chunks of {CHUNK} (streaming, low memory)...") |
| sA_main, sA_usama, sB_main, sB_usama = [], [], [], [] |
| pf = lambda m, a, b: m.predict([np.asarray(a, np.float32), |
| np.asarray(b, np.float32)], |
| batch_size=BATCH, verbose=0).reshape(-1) |
|
|
| for c0 in range(0, len(sub_paths), CHUNK): |
| chunk = sub_paths[c0:c0 + CHUNK] |
| a_mk_rgb, a_mk_ela = [], [] |
| a_uh_rgb, a_uh_ela = [], [] |
| b_rgb_raw, b_ela_raw = [], [] |
| for p in chunk: |
| img = Image.open(p).convert('RGB') |
| a_mk_rgb.append(rgb_default_arr(img)) |
| a_mk_ela.append(ela_multiply_arr(img)) |
| bright = ela_bright_arr(img) |
| lanc = rgb_lanczos_arr(img) |
| a_uh_rgb.append(lanc / 255.0) |
| a_uh_ela.append(bright / 255.0) |
| b_rgb_raw.append(lanc) |
| b_ela_raw.append(bright) |
|
|
| sA_main.extend(pf(km, a_mk_rgb, a_mk_ela)) |
| sA_usama.extend(pf(hm, a_uh_rgb, a_uh_ela)) |
| sB_main.extend(pf(km, b_rgb_raw, b_ela_raw)) |
| sB_usama.extend(pf(hm, np.asarray(b_rgb_raw) / 255., |
| np.asarray(b_ela_raw) / 255.)) |
| print(f" {min(c0 + CHUNK, len(sub_paths))}/{len(sub_paths)}") |
|
|
| sA_main, sA_usama = np.array(sA_main), np.array(sA_usama) |
| sB_main, sB_usama = np.array(sB_main), np.array(sB_usama) |
|
|
| A_main, A_usama = metrics_row(y, sA_main), metrics_row(y, sA_usama) |
| B_main, B_usama = metrics_row(y, sB_main), metrics_row(y, sB_usama) |
|
|
| print_table( |
| "TABLE A β EACH PROJECT AS-DEPLOYED (faithful real-world pipelines)", |
| " main : multiply-ELA + raw[0,255] RGB | usama: brightness-ELA + /255 RGB", |
| A_main, A_usama) |
| print_table( |
| "TABLE B β SAME CORRECTED (brightness) ELA + RGB FOR BOTH", |
| " both fed the identical corrected ELA/RGB images, each in its native range.\n" |
| " NOTE: keras was TRAINED on multiply-ELA, so brightness-ELA is mildly OOD for it.", |
| B_main, B_usama) |
| print("\nDone.") |
|
|
|
|
| if __name__ == "__main__": |
| main() |
|
|