File size: 3,650 Bytes
b23e468
 
c9b7db6
 
b23e468
 
 
c9b7db6
b23e468
 
 
 
c9b7db6
 
 
 
 
 
 
b23e468
c9b7db6
 
 
 
b23e468
c9b7db6
 
 
 
b23e468
c9b7db6
 
 
 
 
b23e468
c9b7db6
 
 
 
 
 
b23e468
c9b7db6
 
 
 
 
 
b23e468
 
 
c9b7db6
b23e468
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c9b7db6
b23e468
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
import os
import torch
import torch.nn as nn
import torch.nn.functional as F
import cv2
import numpy as np
import gradio as gr

# Force CPU inference for Hugging Face free tier
device = torch.device("cpu")

# 1. Exact Architecture matching the trained weights
class SigNetTwinEncoder(nn.Module):
    def __init__(self):
        super(SigNetTwinEncoder, self).__init__()
        self.conv = nn.Sequential(
            nn.Conv2d(1, 32, kernel_size=5, stride=1, padding=2),
            nn.BatchNorm2d(32),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(2, 2),

            nn.Conv2d(32, 64, kernel_size=5, stride=1, padding=2),
            nn.BatchNorm2d(64),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(2, 2),

            nn.Conv2d(64, 128, kernel_size=3, stride=1, padding=1),
            nn.BatchNorm2d(128),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(2, 2)
        )
        self.fc = nn.Sequential(
            nn.Linear(128 * 27 * 19, 256),
            nn.ReLU(inplace=True),
            nn.Dropout(0.3),
            nn.Linear(256, 128)
        )

    def forward_once(self, x):
        features = self.conv(x)
        features = features.view(features.size(0), -1)
        embeddings = self.fc(features)
        return F.normalize(embeddings, p=2, dim=1)

    def forward(self, input1, input2):
        out1 = self.forward_once(input1)
        out2 = self.forward_once(input2)
        return out1, out2

# 2. Instantiate and load model weights safely
model = SigNetTwinEncoder().to(device)
model_path = "signet_twin_model.pth"

if os.path.exists(model_path):
    state_dict = torch.load(model_path, map_location=device)
    model.load_state_dict(state_dict)
    model.eval()
    print("Model weights loaded successfully.")
else:
    print(f"Warning: {model_path} not found in the repository root.")

OPTIMAL_THRESHOLD = 0.5581

# 3. Preprocessing function
def preprocess_for_inference(image):
    if image is None:
        return None
    if len(image.shape) == 3:
        gray = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY)
    else:
        gray = image
        
    _, binary = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)
    resized = cv2.resize(binary, (155, 220))
    normalized = resized.astype(np.float32) / 255.0
    tensor = torch.tensor(normalized, dtype=torch.float32).unsqueeze(0).unsqueeze(0)
    return tensor.to(device)

# 4. Verification Logic
def verify_signatures(ref_img, query_img):
    if ref_img is None or query_img is None:
        return "Please upload both reference and query signatures."
    
    t_ref = preprocess_for_inference(ref_img)
    t_query = preprocess_for_inference(query_img)
    
    with torch.no_grad():
        out_ref, out_query = model(t_ref, t_query)
        distance = F.pairwise_distance(out_ref, out_query).item()
        
    if distance <= OPTIMAL_THRESHOLD:
        verdict = "GENUINE MATCH"
        symbol = "✅"
    else:
        verdict = "FORGERY DETECTED"
        symbol = "❌"
        
    return f"{symbol} {verdict}\n\n• Euclidean Distance: {distance:.4f}\n• Decision Threshold: {OPTIMAL_THRESHOLD:.4f}"

# 5. Gradio Interface
interface = gr.Interface(
    fn=verify_signatures,
    inputs=[
        gr.Image(label="Reference Signature (Known Genuine)"),
        gr.Image(label="Query Signature (To be Verified)")
    ],
    outputs=gr.Textbox(label="Biometric Verification Verdict"),
    title="SigNet-Verify: Offline Signature Forgery Detection",
    description="Siamese Neural Network (Twin CNN) for writer-independent biometric signature verification."
)

if __name__ == "__main__":
    interface.launch()