393 words
2 minutes
TCTT25 Misc: Die With a Smile

Die With a Smile (300 pts) - Miscellaneous Write-up#

โจทย์#

Before he passed away, an old man — once a brilliant AI vibe-coder — dumped the fragmented memories of his friendships into a single image.

Within this mosaic of faces, he hid his eternal secret, encoded not in pixels, but in smiles and silence.

In this strange new era, emotion is encryption — and only by teaching your model to truly understand a smile can you hope to unlock what he left behind.

Can your classifier uncover the truth… or will it be lost to grayscale eternity?

Hint:

  • It is the public dataset
  • The QR isn’t broken. You are, until the classifier works.

ดาวน์โหลดไฟล์#

ไฟล์ดาวน์โหลด
Mosaic Image📦 GitHub Folder

ข้อสังเกต#

  • ภาพโมเสกมีขนาด 2112×2112px แบ่งได้ลงตัวเป็น 33×33 ช่อง (QR Version 3)
  • แต่ละ tile (≈62×62 พิกเซล) คือใบหน้าใน dataset CelebA
  • Attribute สำคัญคือ Smiling (index 31) ของ CelebA
  • หากใช้ threshold ค่าเฉลี่ยความสว่างอย่างเดียว ผล QR อาจเพี้ยน
  • ต้องใช้ Smile classifier ที่แม่น (เช่น ResNet18 ที่ finetune 2-class not-smile/smile)

แนวคิดการแก้โจทย์#

  1. แบ่งภาพโมเสก ออกเป็น 33×33 tiles
  2. โหลดโมเดล ResNet18 (2-class) ที่ train บน CelebA (label Smiling)
    • output = 0 (not-smile), 1 (smile)
  3. Preprocess tile → resize 128×128 → normalize
  4. Predict smile:
    • ถ้า pred=1 → ให้ tile เป็น สีดำ
    • ถ้า pred=0 → ให้ tile เป็น สีขาว
  5. รวมผลลัพธ์ → ขยายภาพแบบ nearest → จะได้ QR Code สมบูรณ์
  6. สแกน QR Code → อ่าน flag

ขั้นตอน Implementation#

Step 1: แบ่ง Mosaic เป็น Tiles#

from PIL import Image
import numpy as np
img = Image.open('mosaic.png')
tiles = []
tile_size = 64 # 2112 / 33 ≈ 64
for row in range(33):
for col in range(33):
x = col * tile_size
y = row * tile_size
tile = img.crop((x, y, x + tile_size, y + tile_size))
tiles.append(tile)

Step 2: โหลด Smile Classifier#

import torch
import torchvision.transforms as transforms
from torchvision.models import resnet18
# Load pretrained model (fine-tuned on CelebA Smiling)
model = resnet18(pretrained=False)
model.fc = torch.nn.Linear(512, 2) # 2 classes
model.load_state_dict(torch.load('smile_classifier.pth'))
model.eval()
transform = transforms.Compose([
transforms.Resize((128, 128)),
transforms.ToTensor(),
transforms.Normalize([0.5, 0.5, 0.5], [0.5, 0.5, 0.5])
])

Step 3: Predict และสร้าง QR#

qr_data = []
for tile in tiles:
input_tensor = transform(tile).unsqueeze(0)
with torch.no_grad():
output = model(input_tensor)
pred = output.argmax(1).item()
# smile=1 → black, not-smile=0 → white
qr_data.append(0 if pred == 1 else 255)
# Reshape to 33x33
qr_array = np.array(qr_data).reshape(33, 33).astype(np.uint8)
qr_image = Image.fromarray(qr_array, mode='L')
qr_image = qr_image.resize((330, 330), Image.NEAREST)
qr_image.save('qr_code.png')

Step 4: สแกน QR Code#

import cv2
from pyzbar.pyzbar import decode
qr = cv2.imread('qr_code.png')
data = decode(qr)
print(data[0].data.decode())

Dataset#

CelebA (CelebFaces Attributes Dataset)

  • 200K+ celebrity face images
  • 40 binary attributes including Smiling
  • Public dataset สำหรับ train smile classifier

Tools ที่ใช้#

Toolวัตถุประสงค์
PyTorchDeep learning model
ResNet18CNN architecture
PIL/PillowImage processing
pyzbarQR code decoding

ผลลัพธ์#

สแกน reconstructed QR code เพื่อรับ flag


Credits#

Writeup by netw0rk7 | Original Repo

TCTT25 Misc: Die With a Smile
https://blog.lukkid.dev/posts/tctt25-misc-die-with-a-smile/
Author
LUKKID
Published at
2025-12-13
License
CC BY-NC-SA 4.0