Hướng dẫn toàn diện để hiểu và triển khai các thuật toán nén video từ đầu bằng Python. Tìm hiểu lý thuyết và thực hành đằng sau các bộ giải mã video hiện đại.
Xây dựng một Bộ giải mã Video bằng Python: Đi sâu vào các Thuật toán Nén
Trong thế giới siêu kết nối của chúng ta, video là vua. Từ các dịch vụ phát trực tuyến và hội nghị video đến các nguồn cấp dữ liệu truyền thông xã hội, video kỹ thuật số thống trị lưu lượng truy cập internet. Nhưng làm thế nào có thể gửi một bộ phim độ nét cao qua kết nối internet tiêu chuẩn? Câu trả lời nằm trong một lĩnh vực hấp dẫn và phức tạp: nén video. Trọng tâm của công nghệ này là bộ giải mã video (COder-DECoder), một tập hợp các thuật toán phức tạp được thiết kế để giảm đáng kể kích thước tệp trong khi vẫn duy trì chất lượng hình ảnh.
Mặc dù các bộ giải mã tiêu chuẩn ngành như H.264, HEVC (H.265) và AV1 miễn phí bản quyền là những phần kỹ thuật cực kỳ phức tạp, nhưng bất kỳ nhà phát triển có động lực nào cũng có thể tiếp cận việc hiểu các nguyên tắc cơ bản của chúng. Hướng dẫn này sẽ đưa bạn vào một cuộc hành trình sâu vào thế giới nén video. Chúng ta sẽ không chỉ nói về lý thuyết; chúng ta sẽ xây dựng một bộ giải mã video đơn giản hóa, mang tính giáo dục từ đầu bằng Python. Cách tiếp cận thực hành này là cách tốt nhất để nắm bắt những ý tưởng thanh lịch giúp cho việc phát trực tuyến video hiện đại trở nên khả thi.
Tại sao lại là Python? Mặc dù không phải là ngôn ngữ bạn sử dụng cho một bộ giải mã thương mại hiệu suất cao, thời gian thực (thường được viết bằng C/C++ hoặc thậm chí là assembly), nhưng khả năng đọc của Python và các thư viện mạnh mẽ của nó như NumPy, SciPy và OpenCV làm cho nó trở thành môi trường hoàn hảo để học tập, tạo mẫu và nghiên cứu. Bạn có thể tập trung vào các thuật toán mà không bị sa lầy vào việc quản lý bộ nhớ cấp thấp.
Hiểu các Khái niệm Cốt lõi của Nén Video
Trước khi chúng ta viết một dòng mã nào, chúng ta phải hiểu những gì chúng ta đang cố gắng đạt được. Mục tiêu của nén video là loại bỏ dữ liệu dư thừa. Một video thô, không nén là rất lớn. Một phút video 1080p ở tốc độ 30 khung hình/giây có thể vượt quá 7 GB. Để chế ngự con quái vật dữ liệu này, chúng ta khai thác hai loại dư thừa chính.
Hai Trụ cột của Nén: Dư thừa Không gian và Thời gian
- Dư thừa Không gian (Trong khung hình): Đây là sự dư thừa trong một khung hình duy nhất. Hãy nghĩ đến một mảng lớn bầu trời xanh hoặc một bức tường trắng. Thay vì lưu trữ giá trị màu cho mọi pixel trong khu vực đó, chúng ta có thể mô tả nó hiệu quả hơn. Đây là nguyên tắc tương tự đằng sau các định dạng nén ảnh như JPEG.
- Dư thừa Thời gian (Giữa các khung hình): Đây là sự dư thừa giữa các khung hình liên tiếp. Trong hầu hết các video, cảnh không thay đổi hoàn toàn từ khung hình này sang khung hình tiếp theo. Ví dụ, một người nói chuyện trước một nền tĩnh có lượng dư thừa thời gian rất lớn. Nền vẫn giữ nguyên; chỉ một phần nhỏ của hình ảnh (khuôn mặt và cơ thể của người đó) di chuyển. Đây là nguồn nén quan trọng nhất trong video.
Các Loại Khung hình Chính: I-frame, P-frame và B-frame
Để khai thác dư thừa thời gian, các bộ giải mã không xử lý mọi khung hình một cách bình đẳng. Chúng phân loại chúng thành các loại khác nhau, tạo thành một chuỗi gọi là Nhóm Ảnh (GOP).
- I-frame (Khung hình được mã hóa bên trong): I-frame là một hình ảnh hoàn chỉnh, độc lập. Nó được nén chỉ bằng cách sử dụng dư thừa không gian, giống như JPEG. I-frame đóng vai trò là điểm neo trong luồng video, cho phép người xem bắt đầu phát lại hoặc tìm kiếm một vị trí mới. Chúng là loại khung hình lớn nhất nhưng rất cần thiết để tạo lại video.
- P-frame (Khung hình được dự đoán): P-frame được mã hóa bằng cách xem xét I-frame hoặc P-frame trước đó. Thay vì lưu trữ toàn bộ hình ảnh, nó chỉ lưu trữ sự khác biệt. Ví dụ: nó lưu trữ các hướng dẫn như "lấy khối pixel này từ khung hình cuối cùng, di chuyển nó 5 pixel sang phải và đây là những thay đổi màu sắc nhỏ." Điều này đạt được thông qua một quy trình gọi là ước tính chuyển động.
- B-frame (Khung hình được dự đoán hai chiều): B-frame là hiệu quả nhất. Nó có thể sử dụng cả khung hình trước đó và khung hình tiếp theo làm tài liệu tham khảo để dự đoán. Điều này rất hữu ích cho những cảnh mà một đối tượng tạm thời bị ẩn và sau đó xuất hiện lại. Bằng cách nhìn về phía trước và phía sau, bộ giải mã có thể tạo ra một dự đoán chính xác và tiết kiệm dữ liệu hơn. Tuy nhiên, việc sử dụng các khung hình trong tương lai gây ra một độ trễ nhỏ, khiến chúng ít phù hợp hơn cho các ứng dụng thời gian thực như cuộc gọi video.
Một GOP điển hình có thể trông như thế này: I B B P B B P B B I .... Bộ mã hóa quyết định kiểu khung hình tối ưu để cân bằng hiệu quả nén và khả năng tìm kiếm.
Quy trình Nén: Phân tích Chi tiết Từng Bước
Mã hóa video hiện đại là một quy trình nhiều giai đoạn. Mỗi giai đoạn biến đổi dữ liệu để làm cho nó dễ nén hơn. Hãy cùng xem các bước chính để mã hóa một khung hình duy nhất.

Bước 1: Chuyển đổi Không gian Màu (RGB sang YCbCr)
Hầu hết video bắt đầu trong không gian màu RGB (Đỏ, Lục, Lam). Tuy nhiên, mắt người nhạy cảm hơn nhiều với những thay đổi về độ sáng (luma) so với những thay đổi về màu sắc (chroma). Các bộ giải mã khai thác điều này bằng cách chuyển đổi RGB sang định dạng luma/chroma như YCbCr.
- Y: Thành phần luma (độ sáng).
- Cb: Thành phần chroma chênh lệch màu xanh lam.
- Cr: Thành phần chroma chênh lệch màu đỏ.
Bằng cách tách độ sáng khỏi màu sắc, chúng ta có thể áp dụng lấy mẫu phụ chroma. Kỹ thuật này làm giảm độ phân giải của các kênh màu (Cb và Cr) trong khi vẫn giữ độ phân giải đầy đủ cho kênh độ sáng (Y), mà mắt chúng ta nhạy cảm nhất. Một sơ đồ phổ biến là 4:2:0, loại bỏ 75% thông tin màu sắc mà hầu như không làm giảm chất lượng, đạt được khả năng nén tức thì.
Bước 2: Phân vùng Khung hình (Macroblock)
Bộ mã hóa không xử lý toàn bộ khung hình cùng một lúc. Nó chia khung hình thành các khối nhỏ hơn, thường là 16x16 hoặc 8x8 pixel, được gọi là macroblock. Tất cả các bước xử lý tiếp theo (dự đoán, biến đổi, v.v.) được thực hiện trên cơ sở từng khối.
Bước 3: Dự đoán (Giữa và Trong)
Đây là nơi điều kỳ diệu xảy ra. Đối với mỗi macroblock, bộ mã hóa quyết định có nên sử dụng dự đoán trong khung hình hay giữa các khung hình hay không.
- Đối với I-frame (Dự đoán trong): Bộ mã hóa dự đoán khối hiện tại dựa trên các pixel của các vùng lân cận đã được mã hóa của nó (các khối phía trên và bên trái) trong cùng một khung hình. Sau đó, nó chỉ cần mã hóa sự khác biệt nhỏ (phần dư) giữa dự đoán và khối thực tế.
- Đối với P-frame hoặc B-frame (Dự đoán giữa): Đây là ước tính chuyển động. Bộ mã hóa tìm kiếm một khối phù hợp trong một khung hình tham chiếu. Khi nó tìm thấy kết quả phù hợp nhất, nó ghi lại một vector chuyển động (ví dụ: "di chuyển 10 pixel sang phải, 2 pixel xuống") và tính toán phần dư. Thông thường, phần dư gần bằng không, đòi hỏi rất ít bit để mã hóa.
Bước 4: Biến đổi (ví dụ: Biến đổi Cosin Rời rạc - DCT)
Sau khi dự đoán, chúng ta có một khối dư. Khối này được chạy qua một phép biến đổi toán học như Biến đổi Cosin Rời rạc (DCT). Bản thân DCT không nén dữ liệu, nhưng nó thay đổi cơ bản cách nó được biểu diễn. Nó chuyển đổi các giá trị pixel không gian thành các hệ số tần số. Điều kỳ diệu của DCT là đối với hầu hết các hình ảnh tự nhiên, nó tập trung hầu hết năng lượng hình ảnh chỉ vào một vài hệ số ở góc trên cùng bên trái của khối (các thành phần tần số thấp), trong khi phần còn lại của các hệ số (nhiễu tần số cao) gần bằng không.
Bước 5: Lượng tử hóa
Đây là bước mất mát chính trong quy trình và là chìa khóa để kiểm soát sự đánh đổi giữa chất lượng và tốc độ bit. Khối hệ số DCT đã được biến đổi được chia cho một ma trận lượng tử hóa và các kết quả được làm tròn đến số nguyên gần nhất. Ma trận lượng tử hóa có các giá trị lớn hơn cho các hệ số tần số cao, loại bỏ hiệu quả nhiều trong số chúng về không. Đây là nơi một lượng lớn dữ liệu bị loại bỏ. Một tham số lượng tử hóa cao hơn dẫn đến nhiều số không hơn, nén cao hơn và chất lượng hình ảnh thấp hơn (thường được thấy dưới dạng các tạo tác khối).
Bước 6: Mã hóa Entropy
Giai đoạn cuối cùng là một bước nén không mất dữ liệu. Các hệ số đã được lượng tử hóa, vector chuyển động và các siêu dữ liệu khác được quét và chuyển đổi thành một luồng nhị phân. Các kỹ thuật như Mã hóa Độ dài Chạy (RLE) và Mã hóa Huffman hoặc các phương pháp tiên tiến hơn như CABAC (Mã hóa Số học Nhị phân Thích ứng theo Ngữ cảnh) được sử dụng. Các thuật toán này gán mã ngắn hơn cho các ký hiệu thường xuyên hơn (như nhiều số không được tạo ra bởi lượng tử hóa) và mã dài hơn cho các ký hiệu ít thường xuyên hơn, ép các bit cuối cùng ra khỏi luồng dữ liệu.
Bộ giải mã chỉ đơn giản thực hiện các bước này ngược lại: Giải mã Entropy -> Lượng tử hóa Ngược -> Biến đổi Ngược -> Bù Chuyển động -> Tái tạo khung hình.
Triển khai Bộ giải mã Video Đơn giản hóa bằng Python
Bây giờ, hãy đưa lý thuyết vào thực tế. Chúng ta sẽ xây dựng một bộ giải mã giáo dục sử dụng I-frame và P-frame. Nó sẽ trình bày quy trình cốt lõi: Ước tính Chuyển động, DCT, Lượng tử hóa và các bước giải mã tương ứng.
Tuyên bố từ chối trách nhiệm: Đây là một bộ giải mã *đồ chơi* được thiết kế để học tập. Nó không được tối ưu hóa và sẽ không tạo ra kết quả có thể so sánh với H.264. Mục tiêu của chúng tôi là xem các thuật toán hoạt động.
Điều kiện tiên quyết
Bạn sẽ cần các thư viện Python sau. Bạn có thể cài đặt chúng bằng pip:
pip install numpy opencv-python scipy
Cấu trúc Dự án
Hãy sắp xếp mã của chúng ta thành một vài tệp:
main.py: Tập lệnh chính để chạy quy trình mã hóa và giải mã.encoder.py: Chứa logic cho bộ mã hóa.decoder.py: Chứa logic cho bộ giải mã.utils.py: Các hàm trợ giúp cho video I/O và biến đổi.
Phần 1: Các Tiện ích Cốt lõi (`utils.py`)
Chúng ta sẽ bắt đầu với các hàm trợ giúp cho DCT, Lượng tử hóa và nghịch đảo của chúng. Chúng ta cũng sẽ cần một hàm để chia một khung hình thành các khối.
# utils.py
import numpy as np
from scipy.fftpack import dct, idct
BLOCK_SIZE = 8
# A standard JPEG quantization matrix (scaled for our purposes)
QUANTIZATION_MATRIX = np.array([
[16, 11, 10, 16, 24, 40, 51, 61],
[12, 12, 14, 19, 26, 58, 60, 55],
[14, 13, 16, 24, 40, 57, 69, 56],
[14, 17, 22, 29, 51, 87, 80, 62],
[18, 22, 37, 56, 68, 109, 103, 77],
[24, 35, 55, 64, 81, 104, 113, 92],
[49, 64, 78, 87, 103, 121, 120, 101],
[72, 92, 95, 98, 112, 100, 103, 99]
])
def apply_dct(block):
"""Applies 2D DCT to a block."""
# Center the pixel values around 0
block = block - 128
return dct(dct(block.T, norm='ortho').T, norm='ortho')
def apply_idct(dct_block):
"""Applies 2D Inverse DCT to a block."""
block = idct(idct(dct_block.T, norm='ortho').T, norm='ortho')
# De-center and clip to valid pixel range
return np.round(block + 128).clip(0, 255)
def quantize(dct_block, qp=1):
"""Quantizes a DCT block. qp is a quality parameter."""
return np.round(dct_block / (QUANTIZATION_MATRIX * qp)).astype(int)
def dequantize(quantized_block, qp=1):
"""Dequantizes a block."""
return quantized_block * (QUANTIZATION_MATRIX * qp)
def frame_to_blocks(frame):
"""Splits a frame into 8x8 blocks."""
blocks = []
h, w = frame.shape
for i in range(0, h, BLOCK_SIZE):
for j in range(0, w, BLOCK_SIZE):
blocks.append(frame[i:i+BLOCK_SIZE, j:j+BLOCK_SIZE])
return blocks
def blocks_to_frame(blocks, h, w):
"""Reconstructs a frame from 8x8 blocks."""
frame = np.zeros((h, w), dtype=np.uint8)
k = 0
for i in range(0, h, BLOCK_SIZE):
for j in range(0, w, BLOCK_SIZE):
frame[i:i+BLOCK_SIZE, j:j+BLOCK_SIZE] = blocks[k]
k += 1
return frame
Phần 2: Bộ Mã hóa (`encoder.py`)
Bộ mã hóa là phần phức tạp nhất. Chúng ta sẽ triển khai một thuật toán đối sánh khối đơn giản để ước tính chuyển động và sau đó xử lý I-frame và P-frame.
# encoder.py
import numpy as np
from utils import apply_dct, quantize, frame_to_blocks, BLOCK_SIZE
def get_motion_vectors(current_frame, reference_frame, search_range=8):
"""A simple block matching algorithm for motion estimation."""
h, w = current_frame.shape
motion_vectors = []
for i in range(0, h, BLOCK_SIZE):
for j in range(0, w, BLOCK_SIZE):
current_block = current_frame[i:i+BLOCK_SIZE, j:j+BLOCK_SIZE]
best_match_sad = float('inf')
best_match_vector = (0, 0)
# Search in the reference frame
for y in range(-search_range, search_range + 1):
for x in range(-search_range, search_range + 1):
ref_i, ref_j = i + y, j + x
if 0 <= ref_i <= h - BLOCK_SIZE and 0 <= ref_j <= w - BLOCK_SIZE:
ref_block = reference_frame[ref_i:ref_i+BLOCK_SIZE, ref_j:ref_j+BLOCK_SIZE]
sad = np.sum(np.abs(current_block - ref_block))
if sad < best_match_sad:
best_match_sad = sad
best_match_vector = (y, x)
motion_vectors.append(best_match_vector)
return motion_vectors
def encode_iframe(frame, qp=1):
"""Encodes an I-frame."""
h, w = frame.shape
blocks = frame_to_blocks(frame)
quantized_blocks = []
for block in blocks:
dct_block = apply_dct(block.astype(float))
quantized_block = quantize(dct_block, qp)
quantized_blocks.append(quantized_block)
return {'type': 'I', 'h': h, 'w': w, 'data': quantized_blocks, 'qp': qp}
def encode_pframe(current_frame, reference_frame, qp=1):
"""Encodes a P-frame."""
h, w = current_frame.shape
motion_vectors = get_motion_vectors(current_frame, reference_frame)
quantized_residuals = []
k = 0
for i in range(0, h, BLOCK_SIZE):
for j in range(0, w, BLOCK_SIZE):
current_block = current_frame[i:i+BLOCK_SIZE, j:j+BLOCK_SIZE]
mv_y, mv_x = motion_vectors[k]
ref_block = reference_frame[i+mv_y : i+mv_y+BLOCK_SIZE, j+mv_x : j+mv_x+BLOCK_SIZE]
residual = current_block.astype(float) - ref_block.astype(float)
dct_residual = apply_dct(residual)
quantized_residual = quantize(dct_residual, qp)
quantized_residuals.append(quantized_residual)
k += 1
return {'type': 'P', 'motion_vectors': motion_vectors, 'data': quantized_residuals, 'qp': qp}
Phần 3: Bộ Giải mã (`decoder.py`)
Bộ giải mã đảo ngược quy trình. Đối với P-frame, nó thực hiện bù chuyển động bằng cách sử dụng các vector chuyển động đã lưu trữ.
# decoder.py
import numpy as np
from utils import apply_idct, dequantize, blocks_to_frame, BLOCK_SIZE
def decode_iframe(encoded_frame):
"""Decodes an I-frame."""
h, w = encoded_frame['h'], encoded_frame['w']
qp = encoded_frame['qp']
quantized_blocks = encoded_frame['data']
reconstructed_blocks = []
for q_block in quantized_blocks:
dct_block = dequantize(q_block, qp)
block = apply_idct(dct_block)
reconstructed_blocks.append(block.astype(np.uint8))
return blocks_to_frame(reconstructed_blocks, h, w)
def decode_pframe(encoded_frame, reference_frame):
"""Decodes a P-frame using its reference frame."""
h, w = reference_frame.shape
qp = encoded_frame['qp']
motion_vectors = encoded_frame['motion_vectors']
quantized_residuals = encoded_frame['data']
reconstructed_blocks = []
k = 0
for i in range(0, h, BLOCK_SIZE):
for j in range(0, w, BLOCK_SIZE):
# Decode the residual
dct_residual = dequantize(quantized_residuals[k], qp)
residual = apply_idct(dct_residual)
# Perform motion compensation
mv_y, mv_x = motion_vectors[k]
ref_block = reference_frame[i+mv_y : i+mv_y+BLOCK_SIZE, j+mv_x : j+mv_x+BLOCK_SIZE]
# Reconstruct the block
reconstructed_block = (ref_block.astype(float) + residual).clip(0, 255)
reconstructed_blocks.append(reconstructed_block.astype(np.uint8))
k += 1
return blocks_to_frame(reconstructed_blocks, h, w)
Phần 4: Kết hợp Mọi Thứ với Nhau (`main.py`)
Tập lệnh này điều phối toàn bộ quy trình: đọc video, mã hóa nó từng khung hình và sau đó giải mã nó để tạo ra đầu ra cuối cùng.
# main.py
import cv2
import pickle # For saving/loading our compressed data structure
from encoder import encode_iframe, encode_pframe
from decoder import decode_iframe, decode_pframe
def main(input_path, output_path, compressed_file_path):
cap = cv2.VideoCapture(input_path)
frames = []
while True:
ret, frame = cap.read()
if not ret:
break
# We'll work with the grayscale (luma) channel for simplicity
frames.append(cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY))
cap.release()
# --- ENCODING --- #
print("Encoding...")
compressed_data = []
reference_frame = None
gop_size = 12 # I-frame every 12 frames
for i, frame in enumerate(frames):
if i % gop_size == 0:
# Encode as I-frame
encoded_frame = encode_iframe(frame, qp=2.5)
compressed_data.append(encoded_frame)
print(f"Encoded frame {i} as I-frame")
else:
# Encode as P-frame
encoded_frame = encode_pframe(frame, reference_frame, qp=2.5)
compressed_data.append(encoded_frame)
print(f"Encoded frame {i} as P-frame")
# The reference for the next P-frame needs to be the *reconstructed* last frame
if encoded_frame['type'] == 'I':
reference_frame = decode_iframe(encoded_frame)
else:
reference_frame = decode_pframe(encoded_frame, reference_frame)
with open(compressed_file_path, 'wb') as f:
pickle.dump(compressed_data, f)
print(f"Compressed data saved to {compressed_file_path}")
# --- DECODING --- #
print("\nDecoding...")
with open(compressed_file_path, 'rb') as f:
loaded_compressed_data = pickle.load(f)
decoded_frames = []
reference_frame = None
for i, encoded_frame in enumerate(loaded_compressed_data):
if encoded_frame['type'] == 'I':
decoded_frame = decode_iframe(encoded_frame)
print(f"Decoded frame {i} (I-frame)")
else:
decoded_frame = decode_pframe(encoded_frame, reference_frame)
print(f"Decoded frame {i} (P-frame)")
decoded_frames.append(decoded_frame)
reference_frame = decoded_frame
# --- WRITING OUTPUT VIDEO --- #
h, w = decoded_frames[0].shape
fourcc = cv2.VideoWriter_fourcc(*'mp4v')
out = cv2.VideoWriter(output_path, fourcc, 30.0, (w, h), isColor=False)
for frame in decoded_frames:
out.write(frame)
out.release()
print(f"Decoded video saved to {output_path}")
if __name__ == '__main__':
main('input.mp4', 'output.mp4', 'compressed.bin')
Phân tích Kết quả và Khám phá Thêm
Sau khi chạy tập lệnh `main.py` với tệp `input.mp4`, bạn sẽ nhận được hai tệp: `compressed.bin`, chứa dữ liệu video nén tùy chỉnh của chúng ta và `output.mp4`, video đã được tái tạo. So sánh kích thước của `input.mp4` với `compressed.bin` để xem tỷ lệ nén. Kiểm tra trực quan `output.mp4` để xem chất lượng. Bạn có thể sẽ thấy các tạo tác khối, đặc biệt là với giá trị `qp` cao hơn, đây là dấu hiệu cổ điển của lượng tử hóa.
Đo Chất lượng: Tỷ lệ Tín hiệu trên Nhiễu Đỉnh (PSNR)
Một số liệu khách quan phổ biến để đo chất lượng tái tạo là PSNR. Nó so sánh khung hình gốc với khung hình đã giải mã. PSNR cao hơn thường cho biết chất lượng tốt hơn.
import numpy as np
import math
def calculate_psnr(original, compressed):
mse = np.mean((original - compressed) ** 2)
if mse == 0:
return float('inf')
max_pixel = 255.0
psnr = 20 * math.log10(max_pixel / math.sqrt(mse))
return psnr
Hạn chế và Các Bước Tiếp theo
Bộ giải mã đơn giản của chúng ta là một khởi đầu tuyệt vời, nhưng nó còn lâu mới hoàn hảo. Dưới đây là một số hạn chế và các cải tiến tiềm năng phản ánh sự phát triển của các bộ giải mã trong thế giới thực:
- Ước tính Chuyển động: Tìm kiếm cạn kiệt của chúng ta chậm và cơ bản. Các bộ giải mã thực sử dụng các thuật toán tìm kiếm phân cấp, phức tạp để tìm vector chuyển động nhanh hơn nhiều.
- B-frame: Chúng ta chỉ triển khai P-frame. Việc thêm B-frame sẽ cải thiện đáng kể hiệu quả nén với chi phí tăng độ phức tạp và độ trễ.
- Mã hóa Entropy: Chúng ta chưa triển khai giai đoạn mã hóa entropy thích hợp. Chúng ta chỉ đơn giản chọn các cấu trúc dữ liệu Python. Việc thêm Bộ mã hóa Độ dài Chạy cho các số không đã được lượng tử hóa, sau đó là bộ mã hóa Huffman hoặc Số học, sẽ làm giảm thêm kích thước tệp.
- Bộ lọc Khử khối: Các cạnh sắc nét giữa các khối 8x8 của chúng ta gây ra các tạo tác có thể nhìn thấy. Các bộ giải mã hiện đại áp dụng bộ lọc khử khối sau khi tái tạo để làm mịn các cạnh này và cải thiện chất lượng hình ảnh.
- Kích thước Khối Biến đổi: Các bộ giải mã hiện đại không chỉ sử dụng macroblock 16x16 cố định. Chúng có thể phân vùng khung hình một cách thích ứng thành nhiều kích thước và hình dạng khối khác nhau để phù hợp hơn với nội dung (ví dụ: sử dụng các khối lớn hơn cho các vùng phẳng và các khối nhỏ hơn cho các vùng chi tiết).
Kết luận
Xây dựng một bộ giải mã video, ngay cả một bộ giải mã đơn giản, là một bài tập vô cùng bổ ích. Nó làm sáng tỏ công nghệ cung cấp năng lượng cho một phần quan trọng trong cuộc sống kỹ thuật số của chúng ta. Chúng ta đã đi qua các khái niệm cốt lõi về dư thừa không gian và thời gian, đi qua các giai đoạn thiết yếu của quy trình mã hóa — dự đoán, biến đổi và lượng tử hóa — và triển khai các ý tưởng này bằng Python.
Mã được cung cấp ở đây là một điểm khởi đầu. Tôi khuyến khích bạn thử nghiệm với nó. Hãy thử thay đổi kích thước khối, tham số lượng tử hóa (`qp`) hoặc độ dài GOP. Cố gắng triển khai một lược đồ Mã hóa Độ dài Chạy đơn giản hoặc thậm chí giải quyết thách thức thêm B-frame. Bằng cách xây dựng và phá vỡ mọi thứ, bạn sẽ có được sự đánh giá sâu sắc về sự khéo léo đằng sau những trải nghiệm video liền mạch mà chúng ta thường coi là đương nhiên. Thế giới nén video là rộng lớn và không ngừng phát triển, mang đến vô số cơ hội để học hỏi và đổi mới.