TÓM TẮT

Mở Đầu

Trong lĩnh vực xử lý ảnh và thị giác máy tính, độ dời ảnh (image displacement) là một khái niệm quan trọng, giúp chúng ta mô tả sự chuyển đổi, biến dạng hoặc dịch chuyển của các điểm ảnh trong một hình ảnh so với một hình ảnh tham chiếu. Việc vẽ và biểu diễn độ dời ảnh không chỉ hữu ích trong nghiên cứu học máy (machine learning) mà còn trong các ứng dụng thực tiễn như ghép ảnh (image stitching), bù trễ (motion compensation), chỉnh sửa video, và phân tích chuyển động.

Bài viết này sẽ cung cấp một hướng dẫn chi tiết, gồm:

  1. Giới thiệu lý thuyết về độ dời ảnh và các mô hình phổ biến.
  2. Chuẩn bị môi trường và các công cụ cần thiết.
  3. Thu thập và tiền xử lý dữ liệu.
  4. Các phương pháp tính toán độ dời (optical flow, block matching, feature-based).
  5. Cách vẽ độ dời dưới dạng vector field, heatmap và các biểu diễn trực quan khác.
  6. Triển khai thực tế bằng Python (OpenCV, NumPy, Matplotlib, PyTorch/TensorFlow).
  7. Đánh giá và tối ưu kết quả.
  8. Ứng dụng thực tiễn và một số lưu ý khi làm việc với độ dời ảnh.

Tất cả các phần sẽ được mở rộng chi tiết, kèm ví dụ code, hình minh hoạ và lời khuyên thực tế, để bạn có thể tự tin thực hiện dự án của mình.

1. Kiến Thức Cơ Bản Về Độ Dời Ảnh

1.1. Định Nghĩa

Độ dời ảnh (displacement field) là một hàm D(x, y) = (u(x, y), v(x, y)) mô tả chuyển động của mỗi pixel tại tọa độ (x, y) từ ảnh gốc sang ảnh mục tiêu. Trong đó:

  • u(x, y): độ dời theo trục hoành (horizontal).
  • v(x, y): độ dời theo trục tung (vertical).

Nếu độ dời dương, pixel di chuyển sang phải (hoặc xuống); nếu âm, di chuyển sang trái (hoặc lên).

1.2. Các Mô Hình Độ Dời Phổ Biến

Mô hình Đặc điểm Ứng dụng
Optical Flow Dựa trên giả thuyết độ sáng không đổi, tính toán chuyển động liên tục Phân tích chuyển động video, tracking
Block Matching Chia ảnh thành các khối, tìm khối tương ứng trong ảnh khác Nén video, bù trễ
Feature-Based Matching (SIFT, SURF, ORB) Dùng các điểm đặc trưng để ước lượng độ dời toàn cục Ghép ảnh, panorama
Deep Learning Based (FlowNet, PWC-Net) Mạng nơ-ron học độ dời trực tiếp Ứng dụng thời gian thực, độ chính xác cao

1.3. Tại Sao Cần Vẽ Độ Dời?

Cách Vẽ Độ Dời Ảnh
Cách Vẽ Độ Dời Ảnh
  • Trực quan hoá: Giúp người dùng nhanh chóng hiểu được mô hình chuyển động.
  • Kiểm tra chất lượng: Đánh giá độ chính xác của thuật toán tính độ dời.
  • Giao diện người-máy: Trong các công cụ chỉnh sửa ảnh/video, người dùng có thể thấy “đường mũi tên” chỉ vị trí dịch chuyển.

2. Chuẩn Bị Môi Trường Làm Việc

2.1. Yêu Cầu Phần Cứng

Thành phần Cấu hình tối thiểu Ghi chú
CPU Intel i5 / AMD Ryzen 5 Đủ cho OpenCV truyền thống
GPU NVIDIA GTX 1060 (Compute Capability ≥ 5.0) Cần cho mô hình deep learning
RAM 8 GB Đề nghị 16 GB cho dữ liệu lớn
Lưu trữ SSD 256 GB Tốc độ đọc/ghi nhanh

2.2. Cài Đặt Phần Mềm

Sử dụng Python 3.10+ và các thư viện:

# Tạo môi trường ảo
python -m venv venv_disp
source venv_disp/bin/activate # Linux/macOS
venv_disp\Scripts\activate # Windows # Cài đặt các gói cần thiết
pip install numpy opencv-python matplotlib seaborn tqdm
pip install torch torchvision # nếu dùng PyTorch
pip install tensorflow # nếu dùng TensorFlow
pip install scikit-image

Lưu ý: Đối với OpenCV, nếu muốn hỗ trợ CUDA, cài phiên bản opencv-python-headless kèm opencv-contrib-python.

2.3. Kiểm Tra Cài Đặt

import cv2, torch, numpy as np, matplotlib.pyplot as plt print("OpenCV version:", cv2.__version__)
print("NumPy version:", np.__version__)
print("PyTorch GPU available:", torch.cuda.is_available())

Nếu mọi thứ hiển thị đúng, môi trường đã sẵn sàng.

3. Thu Thập Và Tiền Xử Lý Dữ Liệu

3.1. Nguồn Dữ Liệu

Tên dữ liệu Mô tả Link
KITTI Flow 2026 Dữ liệu video xe hơi, kèm ground-truth optical flow http://www.cvlibs.net/datasets/kitti/eval_flow.php
MPI Sintel Dữ liệu synthetic, đa dạng chuyển động http://sintel.is.tue.mpg.de/
FlyingChairs Dữ liệu synthetic cho học sâu https://github.com/NVIDIA/flownet2-pytorch

Bạn có thể tải bất kỳ bộ dữ liệu nào phù hợp với mục tiêu. Đối với ví dụ, chúng ta sẽ dùng KITTI.

Cách Vẽ Độ Dời Ảnh
Cách Vẽ Độ Dời Ảnh

3.2. Đọc Ảnh Và Flow Ground Truth

import os, cv2
import numpy as np def read_image(path): img = cv2.cvtColor(cv2.imread(path), cv2.COLOR_BGR2RGB) return img def read_flow_kitti(path): """KITTI flow lưu dưới dạng 3-channel PNG, trong đó: R = horizontal, G = vertical, B = validity mask """ flow_png = cv2.imread(path, cv2.IMREAD_UNCHANGED) flow = flow_png:, :, :2.astype(np.float32) # Giá trị lưu ở dạng 16-bit, chuyển sang float và chuẩn hoá flow = (flow - 215) / 64.0 valid = flow_png:, :, 2 == 1 return flow, valid # Ví dụ
img1 = read_image('kitti/testing/image_2/000000_10.png')
img2 = read_image('kitti/testing/image_2/000000_11.png')
flow_gt, mask_gt = read_flow_kitti('kitti/testing/flow_noc/000000_10.png')

3.3. Chuẩn Hóa Kích Thước

Để vẽ độ dời, các ảnh và flow field nên có cùng kích thước. Nếu cần, dùng cv2.resize.

def resize_to(img, size): return cv2.resize(img, size, interpolation=cv2.INTER_LINEAR) target_size = (512, 256) # width, height
img1_res = resize_to(img1, target_size)
img2_res = resize_to(img2, target_size)
flow_res, mask_res = resize_to(flow_gt, target_size), cv2.resize(mask_gt.astype(np.uint8), target_size, interpolation=cv2.INTER_NEAREST).astype(bool)

4. Các Phương Pháp Tính Toán Độ Dời

4.1. Optical Flow Cơ Bản – Farneback

def farneback_flow(img1, img2): gray1 = cv2.cvtColor(img1, cv2.COLOR_RGB2GRAY) gray2 = cv2.cvtColor(img2, cv2.COLOR_RGB2GRAY) flow = cv2.calcOpticalFlowFarneback( gray1, gray2, None, pyr_scale=0.5, levels=3, winsize=15, iterations=3, poly_n=5, poly_sigma=1.2, flags=0 ) return flow

4.2. Deep Learning – PWC‑Net (PyTorch)

Chuẩn bị mô hình: Tải pretrained weights từ repo chính thức.

import torch
from pwcnet import PWCNet # Giả sử bạn đã clone repo và có module device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
model = PWCNet().to(device)
model.eval()
# Load pretrained checkpoint
checkpoint = torch.load('pwcnet-default.pth.tar')
model.load_state_dict(checkpoint'state_dict') def pwcnet_flow(img1, img2): # Chuyển thành tensor, chuẩn hoá 0,1 img1_t = torch.from_numpy(img1.transpose(2,0,1)).unsqueeze(0).float() / 255.0 img2_t = torch.from_numpy(img2.transpose(2,0,1)).unsqueeze(0).float() / 255.0 img1_t, img2_t = img1_t.to(device), img2_t.to(device) with torch.no_grad(): flow = model(img1_t, img2_t)0 # output shape: 2, H, W flow_np = flow.cpu().numpy().transpose(1,2,0) # H, W, 2 return flow_np

4.3. Block Matching – Simple Implementation

def block_matching(img1, img2, block_size=16, search_range=8): h, w = img1.shape:2 flow = np.zeros((h, w, 2), dtype=np.float32) gray1 = cv2.cvtColor(img1, cv2.COLOR_RGB2GRAY) gray2 = cv2.cvtColor(img2, cv2.COLOR_RGB2GRAY) for y in range(0, h - block_size, block_size): for x in range(0, w - block_size, block_size): ref_block = gray1y:y+block_size, x:x+block_size min_sad = 1e9 best_dx, best_dy = 0, 0 for dy in range(-search_range, search_range+1): for dx in range(-search_range, search_range+1): nx, ny = x+dx, y+dy if nx < 0 or ny < 0 or nx+block_size > w or ny+block_size > h: continue cand_block = gray2ny:ny+block_size, nx:nx+block_size sad = np.sum(np.abs(ref_block.astype(np.int32) - cand_block.astype(np.int32))) if sad < min_sad: min_sad = sad best_dx, best_dy = dx, dy flowy:y+block_size, x:x+block_size, 0 = best_dx flowy:y+block_size, x:x+block_size, 1 = best_dy return flow

Lưu ý: Block matching chậm và không mượt, nhưng rất dễ hiểu cho người mới.

5. Cách Vẽ Độ Dời Ảnh

Cách Vẽ Độ Dời Ảnh
Cách Vẽ Độ Dời Ảnh

5.1. Vector Field (Quiver Plot)

import matplotlib.pyplot as plt def plot_quiver(img, flow, step=16, scale=1, title='Vector Field'): plt.figure(figsize=(10,6)) plt.imshow(img) h, w = flow.shape:2 Y, X = np.mgrid0:h:step, 0:w:step U = flow::step, ::step, 0 scale V = flow::step, ::step, 1 scale plt.quiver(X, Y, U, V, color='lime', angles='xy', scale_units='xy', scale=1) plt.title(title) plt.axis('off') plt.show()

Ví dụ:

flow_farneback = farneback_flow(img1_res, img2_res)
plot_quiver(img1_res, flow_farneback, step=12, scale=1, title='Farneback Optical Flow')

5.2. Heatmap – Độ Dời Magnitude

def plot_magnitude(img, flow, mask=None, title='Flow Magnitude'): magnitude = np.linalg.norm(flow, axis=2) plt.figure(figsize=(10,6)) plt.imshow(img, alpha=0.6) if mask is not None: magnitude = np.where(mask, magnitude, np.nan) plt.imshow(magnitude, cmap='inferno', alpha=0.5) plt.colorbar(label='Pixel displacement (px)') plt.title(title) plt.axis('off') plt.show()

5.3. Màu Sắc HSV – Visualize Direction + Magnitude

def flow_to_hsv(flow): h, w = flow.shape:2 hsv = np.zeros((h, w, 3), dtype=np.uint8) # direction angle = np.arctan2(flow...,1, flow...,0) # -pi..pi hsv...,0 = ((angle + np.pi) / (2np.pi) 179).astype(np.uint8) # Hue 0-179 # magnitude mag = np.linalg.norm(flow, axis=2) hsv...,1 = np.clip(mag10, 0, 255).astype(np.uint8) # Saturation hsv...,2 = 255 rgb = cv2.cvtColor(hsv, cv2.COLOR_HSV2RGB) return rgb def plot_hsv_flow(img, flow, title='HSV Flow Visualization'): hsv_flow = flow_to_hsv(flow) overlay = cv2.addWeighted(img, 0.5, hsv_flow, 0.5, 0) plt.figure(figsize=(10,6)) plt.imshow(overlay) plt.title(title) plt.axis('off') plt.show()

5.4. 3D Surface Plot (Matplotlib)

from mpl_toolkits.mplot3d import Axes3D def plot_3d_surface(flow, axis='u'): """axis='u' or 'v' để vẽ thành phần ngang hoặc dọc""" comp = flow...,0 if axis=='u' else flow...,1 h, w = comp.shape X, Y = np.meshgrid(np.arange(w), np.arange(h)) fig = plt.figure(figsize=(12,7)) ax = fig.add_subplot(111, projection='3d') surf = ax.plot_surface(X, Y, comp, cmap='viridis') ax.set_xlabel('X') ax.set_ylabel('Y') ax.set_zlabel(f'Displacement ({axis})') fig.colorbar(surf, shrink=0.5, aspect=5) plt.show()

6. Triển Khai Thực Tế – Dự Án Mini

6️⃣ Bước 1: Tải Dữ Liệu

mkdir data && cd data
wget http://www.cvlibs.net/download.php?file=data_odometry_gray.zip -O odometry.zip
unzip odometry.zip

6️⃣ Bước 2: Tính Độ Dời Bằng PWC‑Net

Cách Vẽ Độ Dời Ảnh
Cách Vẽ Độ Dời Ảnh
flow_pwc = pwcnet_flow(img1_res, img2_res)
plot_quiver(img1_res, flow_pwc, step=10, scale=0.5, title='PWC‑Net Flow')
plot_hsv_flow(img1_res, flow_pwc, title='PWC‑Net HSV')

6️⃣ Bước 3: Đánh Giá So Với Ground‑Truth

def endpoint_error(flow_est, flow_gt, mask): err = np.linalg.norm(flow_est - flow_gt, axis=2) return np.mean(errmask) epe_farneback = endpoint_error(flow_farneback, flow_res, mask_res)
epe_pwc = endpoint_error(flow_pwc, flow_res, mask_res)
print(f"EPE Farneback: {epe_farneback:.3f} px")
print(f"EPE PWC‑Net: {epe_pwc:.3f} px")

Kết quả thường cho thấy PWC‑Net đạt EPE < 2px trên KITTI, trong khi Farneback khoảng 4‑5px.

6️⃣ Bước 4: Lưu Đồ Thị Độ Dời

def save_quiver(img, flow, path): plt.figure(figsize=(10,6)) plt.imshow(img) step = 12 Y, X = np.mgrid0:flow.shape0:step, 0:flow.shape1:step U = flow::step, ::step, 0 V = flow::step, ::step, 1 plt.quiver(X, Y, U, V, color='cyan', angles='xy', scale_units='xy', scale=1) plt.axis('off') plt.savefig(path, bbox_inches='tight', pad_inches=0, dpi=300) plt.close() save_quiver(img1_res, flow_pwc, 'output/pwc_quiver.png')

7. Đánh Giá, Tối Ưu Hóa Và Xử Lý Lỗi

7.1. Các Thước Đánh Giá

Thước đo Công thức Ý nghĩa
EPE (Endpoint Error) mean( sqrt( (u‑û)² + (v‑v̂)² ) ) Độ lệch trung bình pixel
AAE (Angular Error) arccos( (u·û + v·v̂ + 1) / ( sqrt(u²+v²+1)·sqrt(û²+v̂²+1) ) ) Lỗi góc, hữu ích khi magnitude lớn
Fl‑Error %pixels có EPE > 3px Đánh giá mức độ “outlier”

7.2. Thông Tin Thêm Để Cải Thiện Kết Quả

  1. Pyramid Scales: Tăng số lớp pyramid trong Farneback hoặc PWC‑Net giúp bắt được chuyển động lớn.
  2. Regularization: Áp dụng bộ lọc median hoặc TV‑L1 để làm mịn flow.
  3. Data Augmentation: Khi huấn luyện mô hình deep learning, dùng xoay, zoom, noise để tăng khả năng tổng quát.
  4. Post‑processing: Sử dụng forward‑backward consistency check để loại bỏ outlier.

7.3. Xử Lý Các Vấn Đề Thường Gặp

Vấn đề Nguyên nhân Giải pháp
Flow bị nhiễu ở vùng tối Độ sáng không đủ để tính gradient Thêm tiền xử lý tăng độ tương phản (CLAHE)
Mất chi tiết ở biên Block matching không có block tham chiếu Dùng overlapping blocks hoặc padding
Độ dời quá lớn gây “wrap‑around” Giá trị flow vượt giới hạn lưu trữ Chuẩn hoá lại hoặc cắt giá trị (clipping)
GPU memory overflow Mô hình quá lớn hoặc batch size > 1 Giảm kích thước ảnh, dùng torch.cuda.empty_cache()

8. Ứng Dụng Thực Tiễn

Cách Vẽ Độ Dời Ảnh
Cách Vẽ Độ Dời Ảnh

8.1. Ghép Ảnh Panorama

  • Bước 1: Tính độ dời (feature‑based hoặc optical flow) giữa các khung liên tiếp.
  • Bước 2: Dùng RANSAC để lọc outlier, ước lượng ma trận homography.
  • Bước 3: Áp dụng warp và blend.

8.2. Bù Trễ Video (Video Stabilization)

  • Tính optical flow giữa khung hiện tại và khung tham chiếu.
  • Tích hợp độ dời để có trajectory, sau đó smoothing trajectory và warp lại video.

8.3. Phân Tích Chuyển Động Y Tế

Trong y học, độ dời ảnh giúp đo biến dạng mô (e.g., cardiac MRI). Các bước:

  1. Thu thập series ảnh tim.
  2. Tính optical flow (Thường dùng Lucas‑Kanade hoặc DeepFlow).
  3. Vẽ vector field để đánh giá co giãn cơ tim.

8.4. Thực Tế Trong Game Engine

Engine như Unity hoặc Unreal cho phép nhập displacement maps để tạo hiệu ứng nước, gió. Độ dời ảnh được chuyển thành normal map để chiếu sáng.

9. Tổng Kết

  • Độ dời ảnh là công cụ mạnh mẽ để mô tả chuyển động pixel‑cấp.
  • Chúng ta đã đi qua lý thuyết, công cụ, các thuật toán (Farneback, Block Matching, PWC‑Net) và cách vẽ (quiver, heatmap, HSV, 3D).
  • Python + OpenCV + PyTorch cung cấp môi trường linh hoạt, cho phép vừa học thuật vừa triển khai thực tế.
  • Đánh giá chuẩn (EPE, AAE) giúp so sánh các phương pháp, còn tối ưu hoá (pyramid, regularization) nâng cao chất lượng.
  • Độ dời ảnh không chỉ là khái niệm nghiên cứu mà còn ứng dụng rộng rãi: panorama, ổn định video, y tế, game.

Hy vọng bài viết chi tiết trên sẽ là cẩm nang toàn diện cho bạn trong việc hiểu, tính toán và trực quan hoá độ dời ảnh. Khi đã nắm vững các bước, bạn có thể mở rộng sang các dự án phức tạp hơn, chẳng hạn như multiview stereo, scene flow, hay neural rendering. Chúc bạn thành công!

Rate this post
Mục nhập này đã được đăng trong Blog. Đánh dấu trang permalink.

Để lại một bình luận

Email của bạn sẽ không được hiển thị công khai. Các trường bắt buộc được đánh dấu *