eMilnc commited on
Commit
d8f1c1b
·
verified ·
1 Parent(s): 4cd9506

Sync from GitHub via hub-sync

Browse files
Files changed (3) hide show
  1. src/ViTAE_library.py +0 -188
  2. src/test_model.py +0 -171
  3. src/test_model_tpl.py +0 -173
src/ViTAE_library.py DELETED
@@ -1,188 +0,0 @@
1
- import torch
2
- import torch.nn as nn
3
- import torch.optim as optim
4
- from torch.utils.data import Dataset, DataLoader
5
- from pathlib import Path
6
- from tqdm import tqdm
7
- import random
8
- import matplotlib.pyplot as plt
9
-
10
- class IrisTensorDataset(Dataset):
11
- def __init__(self, file_list):
12
- self.file_list = file_list
13
-
14
- def __len__(self):
15
- return len(self.file_list)
16
-
17
- def __getitem__(self, idx):
18
- tensor_path = self.file_list[idx]
19
- img_tensor = torch.load(tensor_path)
20
- return img_tensor, img_tensor
21
-
22
-
23
- class PatchEmbedding(nn.Module):
24
- def __init__(self, in_channels=1, patch_size=16, embed_dim=256):
25
- super().__init__()
26
- self.patch_size = patch_size
27
- self.proj = nn.Conv2d(in_channels, embed_dim, kernel_size=patch_size, stride=patch_size)
28
-
29
- def forward(self, x):
30
- x = self.proj(x)
31
- x = x.flatten(2)
32
- x = x.transpose(1, 2)
33
- return x
34
-
35
- class ViTAutoencoder(nn.Module):
36
- def __init__(self, img_size=224, patch_size=16, in_channels=1, embed_dim=256, latent_dim=512, num_heads=16, num_layers=6):
37
- super().__init__()
38
- self.patch_size = patch_size
39
- self.num_patches = (img_size // patch_size) ** 2
40
- self.embed_dim = embed_dim
41
-
42
- self.patch_embed = PatchEmbedding(in_channels, patch_size, embed_dim)
43
- self.encoder_pos_embed = nn.Parameter(torch.zeros(1, self.num_patches, embed_dim))
44
-
45
- encoder_layer = nn.TransformerEncoderLayer(d_model=embed_dim, nhead=num_heads, dim_feedforward=embed_dim*4, batch_first=True)
46
- self.transformer_encoder = nn.TransformerEncoder(encoder_layer, num_layers=num_layers)
47
-
48
- self.to_latent = nn.Linear(self.num_patches * embed_dim,latent_dim)
49
- self.from_latent = nn.Linear(latent_dim,self.num_patches * embed_dim)
50
-
51
- self.decoder_pos_embed = nn.Parameter(torch.zeros(1, self.num_patches, embed_dim))
52
- decoder_layer = nn.TransformerDecoderLayer(d_model=embed_dim, nhead=num_heads, dim_feedforward=embed_dim*4, batch_first=True)
53
- self.transformer_decoder = nn.TransformerDecoder(decoder_layer, num_layers=num_layers)
54
-
55
- self.head = nn.Linear(embed_dim, patch_size * patch_size * in_channels)
56
-
57
- def encode(self, x):
58
- x = self.patch_embed(x) + self.encoder_pos_embed
59
- x = self.transformer_encoder(x)
60
- x = x.flatten(1)
61
- latent = self.to_latent(x)
62
- return latent
63
-
64
- def decode(self, latent, batch_size):
65
- x = self.from_latent(latent)
66
- x = x.view(batch_size, self.num_patches, self.embed_dim)
67
- x = x + self.decoder_pos_embed
68
- x = self.transformer_decoder(x, x)
69
- x = self.head(x)
70
-
71
- grid_size = int(self.num_patches ** 0.5)
72
- x = x.view(batch_size, grid_size, grid_size, 1, self.patch_size, self.patch_size)
73
- x = x.permute(0, 3, 1, 4, 2, 5).contiguous()
74
- recon_img = x.view(batch_size, 1, grid_size * self.patch_size, grid_size * self.patch_size)
75
- return recon_img
76
-
77
- def forward(self, x):
78
- batch_size = x.shape[0]
79
- latent = self.encode(x)
80
- recon_img = self.decode(latent, batch_size)
81
- return recon_img
82
-
83
-
84
- def trainModel(max_epoch, model, optimizer, criterion, train_loader, val_loader, train_loss_list, val_loss_list, SAVE_PATH, PATIENCE):
85
- device = 'cuda' if torch.cuda.is_available() else 'cpu'
86
- for epoch in range(1, max_epoch + 1):
87
- # -- Training Phase --
88
- model.to(device)
89
- model.train()
90
- running_train_loss = 0.0
91
-
92
- train_bar = tqdm(train_loader, desc=f"Epoch {epoch}/{max_epoch} [Train]")
93
- for inputs, targets in train_bar:
94
- inputs, targets = inputs.to(device), targets.to(device)
95
- optimizer.zero_grad()
96
- outputs = model(inputs)
97
- loss = criterion(outputs, targets)
98
- loss.backward()
99
- optimizer.step()
100
-
101
- running_train_loss += loss.item() * inputs.size(0)
102
- train_bar.set_postfix({'Loss': f"{loss.item():.4f}"})
103
-
104
- epoch_train_loss = running_train_loss / (len(train_loader) * train_loader.batch_size)
105
- train_loss_list.append(epoch_train_loss)
106
-
107
- # -- Validation Phase --
108
- model.eval()
109
- running_val_loss = 0.0
110
- with torch.no_grad():
111
- for inputs, targets in val_loader:
112
- inputs, targets = inputs.to(device), targets.to(device)
113
- outputs = model(inputs)
114
- loss = criterion(outputs, targets)
115
- running_val_loss += loss.item() * inputs.size(0)
116
-
117
- epoch_val_loss = running_val_loss / len(val_loader) * val_loader.batch_size
118
- val_loss_list.append(epoch_val_loss)
119
-
120
- print(f"Summary - Train Loss: {epoch_train_loss:.5f} | Val Loss: {epoch_val_loss:.5f}")
121
-
122
- # -- Autosave & Early Stopping Logic --
123
- if epoch_val_loss < best_val_loss:
124
- best_val_loss = epoch_val_loss
125
- epochs_without_improvement = 0
126
- torch.save(model.state_dict(), SAVE_PATH)
127
- print(f"--> Validation improved. Model saved to {SAVE_PATH}")
128
- else:
129
- epochs_without_improvement += 1
130
- print(f"--> No improvement for {epochs_without_improvement} epoch(s).")
131
-
132
- if epochs_without_improvement >= PATIENCE:
133
- print(f"\n[Early Stopping Triggered] Stopping at epoch {epoch} due to tolerance limits.")
134
- break
135
-
136
- plt.figure(figsize=(10, 6))
137
- plt.plot(range(1, len(train_loss_list) + 1), train_loss_list, label='Training Loss', marker='o')
138
- plt.plot(range(1, len(val_loss_list) + 1), val_loss_list, label='Validation Loss', marker='x')
139
- plt.title('Training and Validation Loss over Epochs')
140
- plt.xlabel('Epochs')
141
- plt.ylabel('Mean Squared Error (MSE)')
142
- plt.legend()
143
- plt.grid(True)
144
- plt.show()
145
-
146
-
147
- def ViTAEModelTest(data, model, type):
148
- device = 'cuda' if torch.cuda.is_available() else 'cpu'
149
- model.to(device)
150
- criterion = nn.MSELoss()
151
- if type.lower() == 'batch':
152
- running_test_loss = 0.0
153
- with torch.no_grad():
154
- for inputs, targets in tqdm(data, desc="Evaluating Test Set"):
155
- inputs, targets = inputs.to(device), targets.to(device)
156
- outputs = model(inputs)
157
- loss = criterion(outputs, targets)
158
- running_test_loss += loss.item() * inputs.size(0)
159
-
160
- final_test_loss = running_test_loss / len(data) * data.batch_size
161
- print(f"\nFinal Test Set Loss (MSE): {final_test_loss:.5f}")
162
-
163
- elif type.lower() == 'single':
164
- original_data=data.unsqueeze(0)
165
- recon_data=model(original_data)
166
-
167
- original_array=original_data.squeeze().detach().cpu().numpy()
168
- recon_array=recon_data.squeeze().detach().cpu().numpy()
169
- diff_img_from_file = original_array - recon_array
170
-
171
- fig, axes = plt.subplots(1, 3, figsize=(10, 5))
172
-
173
- # 4. Plot the first image on the left axis (axes[0])
174
- axes[0].imshow(original_array, cmap='gray')
175
- axes[0].set_title('Original Image')
176
- axes[0].axis('off') # Optional: hides grid lines and pixel numbers
177
-
178
- # 5. Plot the second image on the right axis (axes[1])
179
- axes[1].imshow(recon_array, cmap='gray')
180
- axes[1].set_title('Reconstructed Image')
181
- axes[1].axis('off') # Optional: hides grid lines and pixel numbers
182
-
183
- axes[2].imshow(diff_img_from_file, cmap='gray')
184
- axes[2].set_title('Difference Image')
185
- axes[2].axis('off')
186
- # 6. Display the plot cleanly
187
- plt.tight_layout() # Prevents labels/titles from overlapping
188
- plt.show()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/test_model.py DELETED
@@ -1,171 +0,0 @@
1
- """
2
- Iris Matching Model Tester
3
- ===========================
4
- ทดสอบโมเดลจับคู่ม่านตา (ViTAutoencoder + MatchCNN_V2)
5
-
6
- วิธีใช้:
7
- python test_model.py <ภาพม่านตา1> <ภาพม่านตา2>
8
-
9
- ตัวอย่าง:
10
- python test_model.py sample_A.jpg sample_B.jpg
11
-
12
- ไฟล์ที่ต้องมีอยู่ในโฟลเดอร์เดียวกับสคริปต์นี้ (หรือแก้ path ด้านล่าง):
13
- - best_match_cnn.pth (มีอยู่แล้ว)
14
- - best_vit_autoencoder.pth (ยังไม่มี - ต้องดาวน์โหลดจาก Drive: IrisPeriNectec/)
15
- - ViTAE_library.py (ยังไม่มี - ต้องดาวน์โหลดจาก Drive: IrisPeriNectec/)
16
- """
17
-
18
- import os
19
- import sys
20
- import argparse
21
-
22
- import torch
23
- import torch.nn as nn
24
- import torch.nn.functional as F
25
- from torchvision import transforms
26
- from PIL import Image
27
-
28
- # ---------------------------------------------------------------------------
29
- # Config — แก้ path ตรงนี้ถ้าไฟล์ไม่ได้อยู่โฟลเดอร์เดียวกับสคริปต์
30
- # ---------------------------------------------------------------------------
31
- SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
32
- AUTOENCODER_WEIGHTS = os.path.join(SCRIPT_DIR, "..", "Model", "best_vit_autoencoder.pth")
33
- MATCHCNN_WEIGHTS = os.path.join(SCRIPT_DIR, "..", "Model", "best_match_cnn.pth")
34
- VITAE_LIBRARY_DIR = SCRIPT_DIR # โฟลเดอร์ที่มี ViTAE_library.py
35
- MATCH_THRESHOLD = 0.5
36
-
37
-
38
- # ---------------------------------------------------------------------------
39
- # MatchCNN_V2 — copy มาจาก NewGoodcnnV1.ipynb / Testcnn.ipynb ทุกตัวอักษร
40
- # ---------------------------------------------------------------------------
41
- class MatchCNN_V2(nn.Module):
42
- def __init__(self):
43
- super().__init__()
44
- self.cnn_extractor = nn.Sequential(
45
- nn.Conv2d(2, 32, 5, padding=2),
46
- nn.ReLU(),
47
- nn.MaxPool2d(2),
48
- nn.Conv2d(32, 64, 3, padding=1),
49
- nn.ReLU(),
50
- nn.MaxPool2d(2),
51
- nn.Conv2d(64, 128, 3, padding=1),
52
- nn.ReLU(),
53
- nn.MaxPool2d(2),
54
- nn.Flatten(),
55
- )
56
- self.fc_input_size = 100352
57
-
58
- self.classifier = nn.Sequential(
59
- nn.Linear(self.fc_input_size + 1, 256),
60
- nn.ReLU(),
61
- nn.Dropout(0.5),
62
- nn.Linear(256, 1),
63
- nn.Sigmoid(),
64
- )
65
-
66
- def forward(self, input1, input2, cos_sim):
67
- combined_imgs = torch.cat([input1, input2], dim=1)
68
- cnn_features = self.cnn_extractor(combined_imgs)
69
- fused = torch.cat([cnn_features, cos_sim], dim=1)
70
- return self.classifier(fused).squeeze()
71
-
72
-
73
- def check_dependencies():
74
- """เช็คว่าไฟล์ที่จำเป็นครบไหมก่อนรัน แจ้ง error ที่อ่านง่าย"""
75
- missing = []
76
- if not os.path.exists(MATCHCNN_WEIGHTS):
77
- missing.append(f" - {MATCHCNN_WEIGHTS}")
78
- if not os.path.exists(AUTOENCODER_WEIGHTS):
79
- missing.append(f" - {AUTOENCODER_WEIGHTS}")
80
- vitae_path = os.path.join(VITAE_LIBRARY_DIR, "ViTAE_library.py")
81
- if not os.path.exists(vitae_path):
82
- missing.append(f" - {vitae_path}")
83
-
84
- if missing:
85
- print("❌ ไฟล์ที่จำเป็นยังไม่ครบ ขาดไฟล์ต่อไปนี้:")
86
- for m in missing:
87
- print(m)
88
- print(
89
- "\nไปดาวน์โหลดจาก Google Drive โฟลเดอร์ 'IrisPeriNectec' "
90
- "(best_vit_autoencoder.pth และ ViTAE_library.py) "
91
- "แล้ววางไว้ในโฟลเดอร์เดียวกับสคริปต์นี้ครับ"
92
- )
93
- sys.exit(1)
94
-
95
-
96
- def load_models(device):
97
- check_dependencies()
98
-
99
- sys.path.insert(0, VITAE_LIBRARY_DIR)
100
- from ViTAE_library import ViTAutoencoder # noqa: E402 (import ต้องมาหลัง sys.path.insert)
101
-
102
- print("กำลังโหลด ViTAutoencoder ...")
103
- autoencoder = ViTAutoencoder(
104
- img_size=224,
105
- patch_size=16,
106
- in_channels=1,
107
- embed_dim=256,
108
- latent_dim=512,
109
- num_heads=16,
110
- num_layers=6,
111
- ).to(device)
112
- autoencoder.load_state_dict(torch.load(AUTOENCODER_WEIGHTS, map_location=device))
113
- autoencoder.eval()
114
-
115
- print("กำลังโหลด MatchCNN_V2 ...")
116
- model = MatchCNN_V2().to(device)
117
- model.load_state_dict(torch.load(MATCHCNN_WEIGHTS, map_location=device))
118
- model.eval()
119
-
120
- print("✅ โหลดโมเดลครบแล้ว\n")
121
- return autoencoder, model
122
-
123
-
124
- def predict_pair(path1, path2, autoencoder, model, device, transform):
125
- """คืนค่า match probability (0-1) ของภาพม่านตาสองรูป"""
126
- for p in (path1, path2):
127
- if not os.path.exists(p):
128
- raise FileNotFoundError(f"ไม่พบไฟล์ภาพ: {p}")
129
-
130
- img1 = transform(Image.open(path1).convert("L")).unsqueeze(0).to(device)
131
- img2 = transform(Image.open(path2).convert("L")).unsqueeze(0).to(device)
132
-
133
- with torch.no_grad():
134
- t1 = autoencoder(img1)
135
- t2 = autoencoder(img2)
136
- cos_sim = F.cosine_similarity(t1.view(1, -1), t2.view(1, -1), dim=1).unsqueeze(1)
137
- prob = model(t1, t2, cos_sim)
138
-
139
- return prob.item()
140
-
141
-
142
- def main():
143
- parser = argparse.ArgumentParser(description="ทดสอบโมเดลจับคู่ม่านตา")
144
- parser.add_argument("image1", help="path ภาพม่านตารูปที่ 1")
145
- parser.add_argument("image2", help="path ภาพม่านตารูปที่ 2")
146
- args = parser.parse_args()
147
-
148
- device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
149
- print(f"ใช้งานบน device: {device}\n")
150
-
151
- autoencoder, model = load_models(device)
152
-
153
- transform = transforms.Compose(
154
- [transforms.Resize((224, 224)), transforms.ToTensor()]
155
- )
156
-
157
- prob = predict_pair(args.image1, args.image2, autoencoder, model, device, transform)
158
-
159
- print("=" * 45)
160
- print(f"ภาพ 1: {args.image1}")
161
- print(f"ภาพ 2: {args.image2}")
162
- print(f"Match probability: {prob:.4f}")
163
- if prob > MATCH_THRESHOLD:
164
- print("ผลลัพธ์: ✅ MATCH (น่าจะเป็นคนเดียวกัน)")
165
- else:
166
- print("ผลลัพธ์: ❌ NO MATCH (น่าจะคนละคน)")
167
- print("=" * 45)
168
-
169
-
170
- if __name__ == "__main__":
171
- main()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/test_model_tpl.py DELETED
@@ -1,173 +0,0 @@
1
- """
2
- Iris Matching Model Tester — TPL (Template) Pipeline
3
- ======================================================
4
- ทดสอบโมเดลจับคู่ม่านตา เวอร์ชัน Template-based
5
- (ViTAutoencoder.encode() -> 512-dim template -> FC matcher)
6
-
7
- วิธีใช้:
8
- python test_model_tpl.py <ภาพม่านตา1> <ภาพม่านตา2>
9
-
10
- ไฟล์ที่ต้องมีอยู่ในโฟลเดอร์เดียวกับสคริปต์นี้:
11
- - scar_encoder_triplet.pth (ViT encoder เทรนด้วย triplet loss)
12
- - best_match_cnn(TPL).pth (FC matcher — ชื่อไฟล์มีคลา์มีคหว้ง cnn ค้างมาจากเดิม
13
- แต่ตัวจริงเป็น fully-connected)
14
- - ViTAE_library.py (นิยามคลาส ViTAutoencoder)
15
-
16
- หมายเหตุสำคัญ:
17
- คลาส MatchFC_TPL ด้านล่างนี้ "reverse-engineer" มาจากการอ่าน shape ของ
18
- state_dict ใน best_match_cnn(TPL).pth โดยตรง (ไม่มี notebook ต้นฉบับให้ดู)
19
- โครงสร้างชั้น Linear ทุกชั้นตรงกับ state_dict 100% (ตรวจสอบแล้ว)
20
- แต่ตัว activation function ระหว่างชั้น (ReLU) เป็นการอนุมานจากรูปแบบเดียวกับ
21
- MatchCNN_V2 ตัวเดิมของผู้เขียนคนเดียวกัน ถ้าผลลัพธ์ดูแปลกๆ (เช่น probability
22
- กระจุกที่ 0.5 หรือ 0/1 สุดขั้วผิดปกติ) ให้ลองเทียบกับ v2.ipynb/ANNtest.ipynb
23
- ถ้ามีต้นฉบับจริง
24
- """
25
-
26
- import os
27
- import sys
28
- import argparse
29
-
30
- import torch
31
- import torch.nn as nn
32
- import torch.nn.functional as F
33
- from torchvision import transforms
34
- from PIL import Image
35
-
36
- # ---------------------------------------------------------------------------
37
- # Config
38
- # ---------------------------------------------------------------------------
39
- SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
40
- ENCODER_WEIGHTS = os.path.join(SCRIPT_DIR, "..", "Model", "scar_encoder_triplet.pth")
41
- MATCHER_WEIGHTS = os.path.join(SCRIPT_DIR, "..", "Model", "best_match_cnn(TPL).pth")
42
- VITAE_LIBRARY_DIR = SCRIPT_DIR
43
- MATCH_THRESHOLD = 0.5
44
-
45
-
46
- # ---------------------------------------------------------------------------
47
- # MatchFC_TPL — reverse-engineered จาก state_dict ของ best_match_cnn(TPL).pth
48
- #
49
- # feature_processing.0.weight (512, 1024) -> Linear(1024, 512)
50
- # feature_processing.2.weight (256, 512) -> Linear(512, 256)
51
- # classifier.0.weight (128, 257) -> Linear(257, 128)
52
- # classifier.3.weight (1, 128) -> Linear(128, 1)
53
- #
54
- # 1024 = template1(512) + template2(512) ต่อกัน
55
- # 257 = fused_feature(256) + cos_sim(1) ต่อกัน
56
- # ---------------------------------------------------------------------------
57
- class MatchFC_TPL(nn.Module):
58
- def __init__(self):
59
- super().__init__()
60
- self.feature_processing = nn.Sequential(
61
- nn.Linear(1024, 512),
62
- nn.ReLU(),
63
- nn.Linear(512, 256),
64
- nn.ReLU(),
65
- )
66
- self.classifier = nn.Sequential(
67
- nn.Linear(256 + 1, 128),
68
- nn.ReLU(),
69
- nn.Dropout(0.5),
70
- nn.Linear(128, 1),
71
- nn.Sigmoid(),
72
- )
73
-
74
- def forward(self, template1, template2, cos_sim):
75
- combined = torch.cat([template1, template2], dim=1) # (B, 1024)
76
- feat = self.feature_processing(combined) # (B, 256)
77
- fused = torch.cat([feat, cos_sim], dim=1) # (B, 257)
78
- return self.classifier(fused).squeeze()
79
-
80
-
81
- def check_dependencies():
82
- missing = []
83
- if not os.path.exists(ENCODER_WEIGHTS):
84
- missing.append(f" - {ENCODER_WEIGHTS}")
85
- if not os.path.exists(MATCHER_WEIGHTS):
86
- missing.append(f" - {MATCHER_WEIGHTS}")
87
- vitae_path = os.path.join(VITAE_LIBRARY_DIR, "ViTAE_library.py")
88
- if not os.path.exists(vitae_path):
89
- missing.append(f" - {vitae_path}")
90
-
91
- if missing:
92
- print("❌ ไฟล์ที่จำเป็นยังไม่ครบ ขาดไฟล์ต่อไปนี้:")
93
- for m in missing:
94
- print(m)
95
- sys.exit(1)
96
-
97
-
98
- def load_models(device):
99
- check_dependencies()
100
-
101
- sys.path.insert(0, VITAE_LIBRARY_DIR)
102
- from ViTAE_library import ViTAutoencoder # noqa: E402
103
-
104
- print("กำลังโหลด ViT encoder (triplet) ...")
105
- encoder = ViTAutoencoder(
106
- img_size=224,
107
- patch_size=16,
108
- in_channels=1,
109
- embed_dim=256,
110
- latent_dim=512,
111
- num_heads=16,
112
- num_layers=6,
113
- ).to(device)
114
- encoder.load_state_dict(torch.load(ENCODER_WEIGHTS, map_location=device))
115
- encoder.eval()
116
-
117
- print("กำลังโหลด MatchFC_TPL ...")
118
- matcher = MatchFC_TPL().to(device)
119
- matcher.load_state_dict(torch.load(MATCHER_WEIGHTS, map_location=device))
120
- matcher.eval()
121
-
122
- print("✅ โหลดโมเดลครบแล้ว\n")
123
- return encoder, matcher
124
-
125
-
126
- def predict_pair(path1, path2, encoder, matcher, device, transform):
127
- for p in (path1, path2):
128
- if not os.path.exists(p):
129
- raise FileNotFoundError(f"ไม่พบไฟล์ภาพ: {p}")
130
-
131
- img1 = transform(Image.open(path1).convert("L")).unsqueeze(0).to(device)
132
- img2 = transform(Image.open(path2).convert("L")).unsqueeze(0).to(device)
133
-
134
- with torch.no_grad():
135
- # .encode() ให้ template 512 มิติตรงๆ (ไม่ผ่าน decode กลับเป็นภาพ)
136
- t1 = encoder.encode(img1) # (1, 512)
137
- t2 = encoder.encode(img2) # (1, 512)
138
- cos_sim = F.cosine_similarity(t1, t2, dim=1).unsqueeze(1) # (1, 1)
139
- prob = matcher(t1, t2, cos_sim)
140
-
141
- return prob.item()
142
-
143
-
144
- def main():
145
- parser = argparse.ArgumentParser(description="ทดสอบโมเดลจับคู่ม่านตา (TPL pipeline)")
146
- parser.add_argument("image1", help="path ภาพม่านตารูปที่ 1")
147
- parser.add_argument("image2", help="path ภาพม่านตารูปที่ 2")
148
- args = parser.parse_args()
149
-
150
- device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
151
- print(f"ใช้งานบน device: {device}\n")
152
-
153
- encoder, matcher = load_models(device)
154
-
155
- transform = transforms.Compose(
156
- [transforms.Resize((224, 224)), transforms.ToTensor()]
157
- )
158
-
159
- prob = predict_pair(args.image1, args.image2, encoder, matcher, device, transform)
160
-
161
- print("=" * 45)
162
- print(f"ภาพ 1: {args.image1}")
163
- print(f"ภาพ 2: {args.image2}")
164
- print(f"Match probability: {prob:.4f}")
165
- if prob > MATCH_THRESHOLD:
166
- print("ขลลัพธ์: ✅ MATCH (๙่าจะเป็นคนเดียวกัน)")
167
- else:
168
- print("ขลลัพธ์: ❌ NO MATCH (๙่าจะคนละคน)")
169
- print("=" * 45)
170
-
171
-
172
- if __name__ == "__main__":
173
- main()