| from __future__ import annotations |
| import torch |
| import torch.nn.functional as F |
| from einops import rearrange |
|
|
|
|
| class SSIM(torch.nn.Module): |
| def __init__(self, kernel_size: int = 3, K1: float = 0.01, K2: float = 0.03, L: float = 1.0): |
| """ |
| L: dynamic range của dữ liệu ảnh. Ảnh đã chuẩn hóa [0,1] => L=1. |
| K1, K2: hằng số chuẩn của SSIM (mặc định 0.01, 0.03). |
| """ |
| super().__init__() |
| self.C1 = (K1 * L) ** 2 |
| self.C2 = (K2 * L) ** 2 |
| |
| self.pool = torch.nn.AvgPool2d(kernel_size, stride=1, padding=kernel_size // 2, |
| count_include_pad=False) |
|
|
| def forward(self, x: torch.Tensor, y: torch.Tensor) -> torch.Tensor: |
| |
| eps = 1e-6 |
|
|
| mu_x = self.pool(x) |
| mu_y = self.pool(y) |
|
|
| sigma_x = self.pool(x * x) - mu_x * mu_x |
| sigma_y = self.pool(y * y) - mu_y * mu_y |
| sigma_xy = self.pool(x * y) - mu_x * mu_y |
|
|
| |
| sigma_x = torch.clamp(sigma_x, min=0.0) |
| sigma_y = torch.clamp(sigma_y, min=0.0) |
|
|
| num = (2 * mu_x * mu_y + self.C1) * (2 * sigma_xy + self.C2) |
| den = (mu_x * mu_x + mu_y * mu_y + self.C1) * (sigma_x + sigma_y + self.C2) + eps |
| ssim = num / den |
|
|
| |
| ssim_loss_map = (1.0 - ssim) * 0.5 |
| ssim_loss_map = torch.clamp(ssim_loss_map, 0.0, 1.0).mean(dim=1, keepdim=True) |
| return ssim_loss_map |
|
|
|
|
|
|
| def _meshgrid_xy(B, H, W, device): |
| ys, xs = torch.meshgrid( |
| torch.linspace(-1.0, 1.0, H, device=device), |
| torch.linspace(-1.0, 1.0, W, device=device), |
| indexing='ij') |
| grid = torch.stack([xs, ys], dim=-1).unsqueeze(0).expand(B, -1, -1, -1) |
| return grid |
|
|
|
|
| def flow_to_norm_grid(flow): |
| B, _, H, W = flow.shape |
| dx = flow[:, 0:1] * (2.0 / max(W-1, 1)) |
| dy = flow[:, 1:2] * (2.0 / max(H-1, 1)) |
| return rearrange(torch.cat([dx, dy], dim=1), 'b c h w -> b h w c') |
|
|
|
|
| def warp(img, flow): |
| B, C, H, W = img.shape |
| base = _meshgrid_xy(B, H, W, img.device) |
| grid = base + flow_to_norm_grid(flow) |
| return F.grid_sample(img, grid, mode='bilinear', padding_mode='border', align_corners=True) |
|
|
|
|
| def image_gradients(img): |
| dx = img[..., :, 1:] - img[..., :, :-1] |
| dy = img[..., 1:, :] - img[..., :-1, :] |
| dx = F.pad(dx, (0,1,0,0)) |
| dy = F.pad(dy, (0,0,0,1)) |
| return dx, dy |