IBP / src /ml_patch.py
Hà Bảo Nhi
Update
417086a
from src import utils
import cv2
import numpy as np
import os
from glob import glob
from sklearn.neighbors import NearestNeighbors
def extract_patches(lr_folder, hr_folder, patch_size=5, scale=2, overlap=0.5):
"""
Chia ảnh LR và HR thành patch
Args:
lr_folder: folder chứa LR images
hr_folder: folder chứa HR images
patch_size: kích thước patch LR (n x n)
scale: scale factor giữa HR và LR
overlap: tỉ lệ chồng lấn (0~1)
Returns:
LR_patches: list các patch LR theo kênh màu YIQ
HR_patches: list các patch HR theo kênh màu YIQ
"""
LR_patches = []
HR_patches = []
LR_features_patches = []
HR_centered_patches = []
LR_y_means = []
step = int(patch_size * (1 - overlap)) # step size sliding window
lr_images = sorted(glob(os.path.join(lr_folder, '*.png')))
hr_images = sorted(glob(os.path.join(hr_folder, '*.png')))
assert len(lr_images) == len(hr_images), "Số ảnh LR và HR phải bằng nhau"
for lr_path, hr_path in zip(lr_images, hr_images):
lr_img = cv2.imread(lr_path, cv2.IMREAD_COLOR)
hr_img = cv2.imread(hr_path, cv2.IMREAD_COLOR)
lr_img = cv2.cvtColor(lr_img, cv2.COLOR_BGR2RGB)
hr_img = cv2.cvtColor(hr_img, cv2.COLOR_BGR2RGB)
h_lr, w_lr = lr_img.shape[:2]
h_hr, w_hr = hr_img.shape[:2]
# Kiểm tra HR size có đúng scale
assert h_hr == h_lr * scale and w_hr == w_lr * scale, \
f"HR image size must be scale * LR image size: {hr_path}"
lr_img = rgb2yiq(lr_img.astype(np.float32) / 255.0)
hr_img = rgb2yiq(hr_img.astype(np.float32) / 255.0)
lr_y_img = lr_img[:, :, 0] # Lấy kênh Y của ảnh
lr_y_img = img_padding(lr_y_img, padding=2, padding_mode='replicate') # Padding kênh Y, logic đúng cho padding = 2 thôi đừng sửa
lr_img_features = compute_grads(lr_y_img) # Tính features gradient bậc 1 và 2 của ảnh
for i in range(0, h_lr - patch_size + 1, step):
for j in range(0, w_lr - patch_size + 1, step):
lr_patch = lr_img[i:i+patch_size, j:j+patch_size, :]
lr_feature_patch = lr_img_features[i:i+patch_size, j:j+patch_size, :]
# HR patch tương ứng
hr_i, hr_j = i*scale, j*scale
hr_patch = hr_img[hr_i:hr_i+patch_size*scale, hr_j:hr_j+patch_size*scale, :]
# Lưu mean của LR và centered HR
lr_patch_y = lr_patch[:, :, 0]
lr_y_mean = float(np.mean(lr_patch_y))
hr_patch_y = hr_patch[:, :, 0]
hr_y_centered = hr_patch_y - lr_y_mean
LR_patches.append(lr_patch)
LR_features_patches.append(lr_feature_patch)
LR_y_means.append(lr_y_mean)
HR_patches.append(hr_patch)
HR_centered_patches.append(hr_y_centered)
print(f"Extracted {len(LR_patches)} LR patches, {len(HR_patches)} HR patches, {len(LR_features_patches)} LR feature patches, {len(LR_y_means)} LR_Y_means and {len(HR_centered_patches)} HR_centered_patches from {len(lr_images)} image pairs.")
return np.array(LR_patches), np.array(HR_patches), np.array(LR_features_patches), np.array(LR_y_means), np.array(HR_centered_patches)
def process_all_patches(base_dir, output_dir, patch_size=5, overlap=0.5):
"""
Duyệt tất cả scale factor và lưu patch
base_dir: list: base_dir[0] là dir tới degraded_lr, base_dir[1] là dir tới input_hr
"""
os.makedirs(output_dir, exist_ok=True)
scales = [2, 3, 4]
for s in scales:
lr_folder = os.path.join(base_dir[0], f'LR_x{s}')
hr_folder = os.path.join(base_dir[1], f'HR_x{s}')
LR_patches, HR_patches, LR_features_patches, LR_y_mean_patches, HR_centered_patches = extract_patches(lr_folder, hr_folder,
patch_size=patch_size, scale=s,
overlap=overlap)
# Lưu file numpy
np.save(os.path.join(output_dir, f'LR_patches_x{s}.npy'), LR_patches)
np.save(os.path.join(output_dir, f'HR_patches_x{s}.npy'), HR_patches)
np.save(os.path.join(output_dir, f'LR_features_patches_x{s}.npy'), LR_features_patches)
np.save(os.path.join(output_dir, f'LR_y_mean_patches_x{s}.npy'), LR_y_mean_patches)
np.save(os.path.join(output_dir, f'HR_centered_patches_x{s}.npy'), HR_centered_patches)
print(f"Saved normalized YIQ LR/HR patches for scale x{s} to {output_dir}")
def rgb2yiq(rgb):
# Ma trận chuyển đổi từ RGB sang YIQ
transform = np.array([[0.299, 0.587, 0.114],
[0.596, -0.274, -0.322],
[0.211, -0.523, 0.312]])
shape = rgb.shape
flat_rgb = rgb.reshape(-1, 3)
flat_yiq = np.dot(flat_rgb, transform.T)
yiq = flat_yiq.reshape(shape)
return yiq
def yiq2rgb(yiq):
"""
Chuyển mảng YIQ sang RGB
yiq: numpy array (H, W, 3), giá trị float 0~1 (có thể <0 hoặc >1)
Trả về mảng RGB float 0~1
"""
# ma trận chuyển đổi
T = np.array([[1.0, 0.956, 0.621],
[1.0, -0.272, -0.647],
[1.0, -1.106, 1.703]])
# reshape để nhân ma trận: (H*W,3)
shape = yiq.shape
yiq_flat = yiq.reshape(-1, 3)
rgb_flat = yiq_flat @ T.T # nhân ma trận
rgb = rgb_flat.reshape(shape)
return rgb
def img_padding(img, padding=2, padding_mode='zero'):
"""
Padding ảnh
mặc định padding 2 pixel, mode 'zero' mặc định với paper
Args:
imgs: ảnh theo channel Y (đã chuyển sang YIQ)
padding: số pixel cần padding
padding_mode: 'zero' hoặc 'replicate' hoặc 'reflection'
Returns:
padded_img: ảnh đã được padding
"""
if padding_mode == 'zero':
mode='constant'
elif padding_mode == 'replicate':
mode='edge'
elif padding_mode == 'reflection':
mode='symmetric'
else:
raise ValueError(f'Unknown padding mode {padding_mode}')
padded = np.pad(img, ((padding,padding), (padding,padding)), mode = mode)
return padded
def compute_grads(img, padding=2):
"""
Tính gradient bậc 1 và 2 theo 2 chiều x, y
Args:
img: ảnh theo channel Y (đã chuyển sang YIQ và đã được padding)
padding_mod: 'zero' hoặc 'replicate'
Returns:
grad_x: gradient theo chiều x
grad_y: gradient theo chiều y
grad2_x: gradient bậc 2 theo chiều x
grad2_y: gradient bậc 2 theo chiều y
"""
gx = img[:, 3:-1] - img[:, 1:-3]
gy = img[3:-1, :] - img[1:-3, :]
g2x = img[:, 4:] - 2 * img[:, 2:-2] + img[:, :-4]
g2y = img[4:, :] - 2 * img[2:-2, :] + img[:-4, :]
gx = gx[2:-2, :]
gy = gy[:, 2:-2]
g2x = g2x[2:-2, :]
g2y = g2y[:, 2:-2]
H, W = gx.shape
feat = np.zeros((H, W, 4), dtype=np.float32)
feat[:, :, 0] = gx
feat[:, :, 1] = gy
feat[:, :, 2] = g2x
feat[:, :, 3] = g2y
return feat # list các features của ảnh
def build_knn_idx(LR_features_patches, k=5):
"""
Xây dựng chỉ mục KNN từ các patch feature LR
Args:
LR_feature_patches: mảng numpy các patch feature LR
Returns:
knn_model: mô hình KNN đã được huấn luyện
"""
N, h, w, _ = LR_features_patches.shape
feat_vectors = LR_features_patches.reshape(N, -1) # Chuyển mỗi patch thành vector 1D
# fit knn
knn = NearestNeighbors(n_neighbors=k, algorithm='auto', metric='euclidean')
knn.fit(feat_vectors)
return knn, feat_vectors
def query_knn(knn_model, feat_vectors, query_feat, k=5):
"""
Truy vấn KNN để tìm các patch tương tự nhất
Args:
knn_model: mô hình KNN đã được huấn luyện
feat_vectors: mảng numpy các vector feature LR đã được reshape thành 1D (N, h*w*4)
query_feat: vector feature của patch cần truy vấn
Returns:
distances: khoảng cách đến các patch gần nhất
indices: chỉ số của các patch gần nhất trong tập dữ liệu gốc
"""
query_feat = query_feat.reshape(1, -1) # Đảm bảo query_feat là vector 1D
distances, indices = knn_model.kneighbors(query_feat, n_neighbors=k)
return distances[0], indices[0]
def compute_weights(target_vec, neighbor_vecs, epsilon=1e-6):
"""
Tính trọng số cho các patch lân cận dựa trên khoảng cách
Args:
target_vec: vector feature của patch mục tiêu (1D)
neighbor_vecs: mảng numpy các vector feature của các patch lân cận (k, h*w*4)
epsilon: hằng số nhỏ để tránh chia cho 0
Returns:
weights: mảng numpy các trọng số tương ứng với các patch lân cận (k,)
"""
k, D = neighbor_vecs.shape
X = neighbor_vecs.T # (D, k)
x_q = target_vec.reshape(-1, 1) # (D, 1)
diff = np.dot(x_q, np.ones((1, k))) - X # (D, k)
G_q = np.dot(diff.T, diff) # (k, k)
G_q += epsilon * np.eye(k) # Thêm epsilon vào đường chéo chính để tránh chia cho 0
ones = np.ones((k, 1))
weights = (np.linalg.inv(G_q) @ ones) / (ones.T@np.linalg.inv(G_q)@ones)
return weights
def map_hr_patches(neighbor_hr_patches, weights, lr_y_mean):
"""
Ánh xạ các patch HR lân cận thành patch HR mục tiêu
Args:
neighbor_hr_patches: mảng numpy các patch HR lân cận (k, h*scale, w*scale)
weights: mảng numpy các trọng số tương ứng với các patch lân cận (k,)
lr_y_mean: giá trị mean của patch LR mục tiêu
Returns:
hr_patch: patch HR mục tiêu (h*scale, w*scale)
"""
predicted_hr = np.tensordot(weights[:, 0], neighbor_hr_patches, axes=(0,0)) # (h*scale, w*scale)
predicted_hr += lr_y_mean # Thêm mean của patch LR mục tiêu
return predicted_hr
def extract_patches_per_image(lr_img_path, hr_img_path=None, patch_size=5, scale=2, overlap=0.5):
"""
Chia một ảnh LR (và HR nếu có) thành patch.
Args:
lr_img_path: đường dẫn ảnh LR
hr_img_path: đường dẫn ảnh HR (nếu có)
patch_size: kích thước patch LR
scale: scale factor giữa HR và LR
overlap: tỉ lệ chồng lấn
Returns:
LR_patches, LR_feature_patches, LR_y_means, HR_centered_patches
"""
LR_patches, LR_features_patches, LR_y_means = [], [], []
lr_img = cv2.imread(lr_img_path, cv2.IMREAD_COLOR)
lr_img = cv2.cvtColor(lr_img, cv2.COLOR_BGR2RGB).astype(np.float32) / 255.0
lr_img = rgb2yiq(lr_img)
lr_y_img = lr_img[:, :, 0]
lr_y_img_pad = img_padding(lr_y_img, padding=2, padding_mode='replicate')
lr_features = compute_grads(lr_y_img_pad)
step = max(1, int(patch_size*(1-overlap)))
H_lr, W_lr = lr_img.shape[:2]
for i in range(0, H_lr - patch_size + 1, step):
for j in range(0, W_lr - patch_size + 1, step):
lr_patch = lr_img[i:i+patch_size, j:j+patch_size, :]
feat_patch = lr_features[i:i+patch_size, j:j+patch_size, :]
lr_patch_y = lr_patch[:, :, 0]
lr_mean = float(lr_patch_y.mean())
LR_patches.append(lr_patch)
LR_features_patches.append(feat_patch)
LR_y_means.append(lr_mean)
return np.array(LR_patches), np.array(LR_features_patches), np.array(LR_y_means)
def reconstruct_hr_rgb(predicted_y, lr_img_path, scale=2):
"""
Kết hợp kênh Y đã dự đoán với kênh IQ từ ảnh LR để tái tạo ảnh RGB HR
Args:
predicted_y: kênh Y đã dự đoán (H*scale, W*scale)
lr_yiq: ảnh LR theo kênh YIQ (H, W, 3)
scale: scale factor giữa HR và LR
Returns:
hr_rgb: ảnh RGB HR tái tạo (H*scale, W*scale, 3)
"""
lr_rgb = cv2.imread(lr_img_path, cv2.IMREAD_COLOR)
lr_rgb = cv2.cvtColor(lr_rgb, cv2.COLOR_BGR2RGB).astype(np.float32) / 255.0
lr_yiq = rgb2yiq(lr_rgb)
lr_I = lr_yiq[:, :, 1]
lr_Q = lr_yiq[:, :, 2]
H_hr, W_hr = predicted_y.shape
I_hr = cv2.resize(lr_I, (W_hr, H_hr), interpolation=cv2.INTER_CUBIC)
Q_hr = cv2.resize(lr_Q, (W_hr, H_hr), interpolation=cv2.INTER_CUBIC)
HR_YIQ = np.stack([predicted_y, I_hr, Q_hr], axis=-1)
# YIQ -> RGB
predicted_HR_rgb = yiq2rgb(HR_YIQ)
predicted_HR_rgb = np.clip(predicted_HR_rgb, 0, 1)
predicted_HR_rgb_uint8 = (predicted_HR_rgb*255).astype(np.uint8)
return predicted_HR_rgb_uint8