ĐẠI HỌC QUỐC GIA HÀ NỘI
ĐẠI HỌC CÔNG NGHỆ
BÁO CÁO TIỂU LUẬN
ĐỀ TÀI 2.19
NÉN DỮ LIỆU
GVHD: PGS. TS. Trịnh Nhật Tiến
HV: Đỗ Thị Chi
Hà Nội 08/12/2013
MỤC LỤC
!! !"
#$%&'()*+, ')*/012-
#$%&'-3-+4567/
#$%&'.686-3-+4567/9
:;<=
LỜI MỞ ĐẦU
Bảo mật thông tin, nén dữ liệu là công việc cần thiết trong ngành CNTT. Từ thời
xa xưa, con người đã biết gìn giữ thông tin trong quân sự, trong chiến tranh nhằm làm
cho thông tin được an toàn, không lọt vào tay đối phương. Muốn vậy, con người có
nhiều cách để làm cho thông tin truyền đi được gọn nhẹ và an toàn khi lưu thông. Để
thông tin đến tay người nhận một cách bí mật, gọn gàng nhất thì họ phải nén thông tin
lại hay dùng các ký hiệu đặc biệt có quy ước trước.
Ngày nay, với sự tiến bộ của khoa học kỹ thuật, CNTT được nâng lên một tầm
cao mới. Mọi ngành, nghề đều phải ứng dụng CNTT một cách triệt để để phát triển
một cách tốt nhất. Ngay như cả cụm từ “Chính phủ điện tử” mà ta thường nghe cho ta
thấy tầm quan trọng của việc đưa CNTT vào cuộc sống. Không nằm ngoài sự phát
triển có tính quy luật chung của xã hội, CNTT đã và đang làm hết sức mình để phát
triển ngày một tốt hơn, nhanh hơn, nhẹ hơn, gọn hơn… CNTT và viễn thông có mối
liên hệ tương đối chặt chẽ với nhau. Một trong những tiêu chí giúp ngành CNTT phát
triển là sử dụng công nghệ bảo mật, nén dữ liệu, thông tin trong lưu trữ và truyền
thông.
CHƯƠNG 1: TỔNG QUAN VỀ NÉN DỮ LIỆU
1.1. Một số khái niệm
• Nén dữ liệu (Data compression)
Nén dữ liệu là quá trình làm giảm lượng thông tin “dư thừa” trong dữ liệu gốc,
do vậy lượng thông tin thu được sau khi nén thường nhỏ hơn dữ liệu gốc rất nhiều.
Hay ta có thể hiểu: Nén dữ liệu thực chất là một hình thức mã hóa dữ liệu để ghi lại
dòng dữ liệu sao cho tốn ít bộ nhớ hơn mà lại cho phép chúng ta khôi phục lại dữ liệu
ban đầu.
Ngoài thuật ngữ “nén dữ liệu”, do bản chất của kỹ thuật này nó còn có một số tên
gọi khác như: giảm độ dư thừa, mã hóa ảnh gốc.
Từ hơn 2 thập kỷ nay, có rất nhiều kỹ thuật nén đã được công bố trên các tài liệu
về nén và các phần mềm nén dữ liệu đã xuất hiện ngày càng nhiều trên thương trường.
Tuy nhiên, chưa có phương pháp nén nào được coi là phương pháp vạn năng
(Universal) vì nó phụ thuộc vào nhiều yếu tố và bản chất của dữ liệu gốc. Trong bài
báo cáo này, chúng ta không thể hy vọng xem xét tất cả các phương pháp nén. Hơn thế
nữa, các kỹ thuật nén dữ liệu chung đã được trình bày trong nhiều tài liệu chuyên
ngành. Ở đây, chúng ta chỉ đề cập một số phương pháp nén dữ liệu phổ biến.
• Tỷ lệ nén (Compression rate)
Tỷ lệ nén là một trong các đặc trưng quan trọng nhất của mọi phương pháp nén.
Tuy nhiên, cách đánh giá và các kết quả công bố trong các tài liệu cũng cần được quan
tâm xem xét.
Tỷ lệ nén = %
Với r là tỷ số nén được định nghĩa như sau:
r = kích thước dữ liệu gốc/ kích thước dữ liệu thu được sau khi nén.
Như vậy, hiệu suất nén là: (1 – tỷ lệ nén) %
Tuy nhiên, tỷ lệ nén cũng chỉ là một trong những đặc trưng cơ bản của phương
pháp nén. Nhiều khi tỷ lệ nén cao cũng chưa thể nói rằng phương pháp đó là hiệu quả
hơn các phương pháp khác vì còn có các chi phí khác như thời gian, không gian và
thậm chí cả độ phức tạp tính toán. Thí dụ như nén phục vụ trong truyền dữ liệu: vấn đề
đặt ra là hiệu quả nén có tương hợp với đường truyền không.
Cũng cần phân biệt dữ liệu với nén băng truyền. Mục đích chính của nén là giảm
lượng thông tin dư thừa và dẫn tới giảm kích thước dữ liệu. Tuy vậy, đôi khi quá trình
nén cũng làm giảm băng truyền tín hiệu số hóa thấp hơn so với truyền tín hiệu tương
tự.
1.2. Mục đích của nén dữ liệu
Một trong những chức năng chính của máy tính là xử lý dữ liệu và lưu trữ. Bên
cạnh việc xử lý nhanh, người ta còn quan tâm đến việc lưu trữ được nhiều dữ liệu
nhưng lại tiết kiệm được vùng nhớ và giảm chi phí lưu trữ. Về mặt lý thuyết thì các
thiết bị lưu trữ là không có giới hạn nhưng ngày nay do nhu cầu xử lý nhiều tập tin,
nhiều loại dữ liệu trong cùng một tệp do vậy mà kích thước tệp trở nên khá lớn.
Ngày nay, những lợi ích Internet cung cấp rất đa dạng và phong phú trên các lĩnh
vực khác nhau của xã hội như cung cấp, trao đổi thông tin giữa các máy tính, giữa máy
tính với server hoặc giữa các server với nhau. Điều này dẫn đến phải làm như thế nào
để giảm thiểu thời gian, chi phí sử dụng để trao đổi dữ liệu trên mạng. Nó cũng đồng
nghĩa với việc bên cạnh nâng cao chất lượng của các thiết bị truyền dữ liệu trên mạng
thì mặt khác chúng ta phải nghĩ ra một phương pháp nào để việc truyền dữ liệu có hiệu
quả hơn.
Tất cả những vấn đề trên nảy sinh ra khái niệm nén dữ liệu. Một trong những
hình thức nén dữ liệu đầu tiên là hệ chữ Braille, là một hệ chữ dùng phương pháp mã
hóa ký hiệu cho người mù có thể đọc và viết. Ngày nay, nén dữ liệu mang lại nhiều lợi
ích khác nhau mà điển hình là:
- Đối với việc tìm kiếm thì đôi khi tìm kiếm thông tin trên dữ liệu nén lại nhanh
hơn so với việc tìm kiếm thông tin trên dữ liệu không nén vì do dữ liệu lưu
trữ ít nên số phép toán để tìm kiếm giảm và lượng thông tin cao.
- Nén dữ liệu đặc biệt hiệu quả với việc truyền dữ liệu trên mạng. Khi nén dữ
liệu thì chi phí cho việc truyền dữ liệu trên mạng giảm, mặt khác tốc độ
đường truyền sẽ tăng lên vì cùng một lượng thông tin đó thời gian truyền dữ
liệu sẽ giảm.
Do đó, với những ưu điểm trên thì việc nén dữ liệu là giải pháp hợp lý nhất nhằm
mục đích giảm chi phí cho người sử dụng.
>
Tóm lại, những lợi ích mà nén dữ liệu mang lại xoay quanh vấn đề tiết kiệm tối
đa chi phí cho việc mua các thiết bị lưu trữ dữ liệu và cho việc luân chuyển thông tin
trên mạng.
1.3. Các loại dư thừa dữ liệu
Thông thường, hầu hết các tập tin trong máy tính có rất nhiều thông tin dư thừa,
việc thực hiện nén tập tin thực chất là mã hoá lại các tập tin để loại bỏ các thông tin dư
thừa. Việc xác định bản chất các kiểu dư thừa dữ liệu rất có ích cho việc xây dựng các
phương pháp nén dữ liệu khác nhau. Nói một cách khác, các phương pháp nén dữ liệu
khác nhau là do sử dụng các kiểu dư thừa dữ liệu khác nhau. Có thể phân thành 4 kiểu
dư thừa chính:
• Sự phân bố ký tự
Trong một dãy ký tự, có một số ký tự có tần suất xuất hiện nhiều hơn một số
dãy khác. Do vậy, ta có thể mã hóa dữ liệu một cách cô đọng hơn. Các dãy ký tự có
tần suất cao được thay bởi một từ mã nhị phân với số bit nhỏ; ngược lại các dãy có
tần suất thấp được mã hóa bởi từ mã có nhiều bit hơn. Đây chính là bản chất của
phương pháp mã hóa Hufman.
• Sự lặp lại của các ký tự
Trong một số tình huống như dữ liệu ảnh, ký hiệu bít “0” hay bít “1” được lặp
đi lặp lại một số lần. Kỹ thuật nén dùng trong trường hợp này là thay dãy lặp đó bởi
dãy mới gồm 2 thành phần: số lần lặp và ký hiệu dùng để mã. Phương pháp mã hóa
kiểu này có tên là mã hóa loạt dài RLC (Run Length Coding).
• Những mẫu sử dụng tần suất
Có thể có dãy ký hiệu nào đó xuất hiện với tần suất tương đối cao. Do vậy, có
thể mã hóa bởi ít bit hơn. Đây là cơ sở của phương pháp mã hóa kiểu từ điển do
Lempel – Ziv đưa ra và có cải tiến vào năm 1977, 1978 và do đó có tên gọi là
phương pháp nén LZ77, LZ78. Năm 1984, Terry Welch đã cải tiến hiệu quả hơn và
đặt tên là LZW (Lempel – Ziv - Welch).
• Độ dư thừa vị trí
Do sự phụ thuộc lẫn nhau của dữ liệu, đôi khi biết được ký hiệu (giá trị) xuất
hiện tại một vị trí, đồng thời có thể đoán trước sự xuất hiện của các giá trị ở các vị
trí khác nhau một cách phù hợp. Chẳng hạn, ảnh biểu diễn trong dạng lưới hai
chiều, một số điểm ở hàng dọc trong một khối dữ liệu lại xuất hiện trong cùng vị trí
?
ở các hàng khác nhau. Do vậy, thay vì lưu trữ dữ liệu ta chỉ cần lưu trữ vị trí hàng
và cột. Phương pháp nén dựa trên sự dư thừa này gọi là phương pháp mã hóa dự
đoán.
Cách đánh giá độ dư thừa như trên hoàn toàn mang tính trực quan nhằm biểu thị
một cái gì đó xuất hiện nhiều lần. Đối với dữ liệu ảnh, ngoài đặc thù chung đó nó còn
có những đặc thù riêng. Thí dụ như có ứng dụng không cần toàn bộ dữ liệu thô của
ảnh mà chỉ cần có các thông tin đặc trưng biểu diễn ảnh như biên ảnh hay vùng đồng
nhất. Do vậy, có những phương pháp nén riêng cho ảnh dựa vào biến đổi ảnh hay dựa
vào biểu diễn ảnh.
1.4. Một số vấn đề gặp phải khi nén dữ liệu
- Các thuật toán thực hiện trước hết phải giảm chi phí lưu trữ.
- Các thuật toán được thực hiện nhanh, hiệu quả.
Nhìn chung không có phương phát nén tổng quát nào cho kết quả tốt đối với tất
cả các loại tập tin. Với nhiều loại thông tin khác nhau mà ta có các kỹ thuật nén khác
nhau, có hiệu quả khác nhau. Ví dụ như nén tệp văn bản thường tiết kiệm 20% - 50%,
còn đối với tập tin nhị phân khoảng 50% - 90%, tuy nhiên đối với các tập tin ngẫu
nhiên thì lượng không gian tiết kiệm được rất ít hoặc hầu như không tiết kiệm được
( chẳng hạn như tệp *.exe,…). Do dữ liệu có độ dư thừa khác nhau nên mỗi phương
pháp nén nếu áp dụng không đúng sẽ trở nên không cần thiết và không có độ linh hoạt.
1.5. Quá trình nén và giải nén
Quá trình nén và giải nén dữ liệu là một quá trình gồm 2 công đoạn:
- Công đoạn nén: dữ liệu gốc qua bộ mã hóa dữ liệu, bộ mã hóa này thực hiện nén
dữ liệu đến một mức thích hợp cho việc lưu trữ và truyền dẫn thông tin. Quá
trình này sẽ thực hiện việc loại bỏ hay cắt bớt những dữ liệu dư thừa để thu được
thông tin cần thiết nhưng vẫn đảm bảo chất lượng.
- Công đoạn giải nén: dữ liệu nén đi qua bộ giải mã dữ liệu, bộ giải mã thực hiện
giải nén để thu được dữ liệu gốc ban đầu. Việc giải nén này thường phải dựa vào
các thông tin đi kèm theo dữ liệu nén, tùy thuộc vào kiểu nén hay phương pháp
nén mà dữ liệu giải nén được có hoàn toàn giống với dữ liệu gốc ban đầu hay
không.
Tóm lại quá trình nén và giải nén dữ liệu có thể mô tả một cách tóm tắt theo sơ
đồ dưới đây:
9
Sơ đồ nén và giải nén dữ liệu
@
CHƯƠNG 2: CÁC PHƯƠNG PHÁP NÉN DỮ LIỆU
Phân loại các phương pháp nén
Có nhiều cách phân loại các phương pháp nén khác nhau.
2.1.1. Cách thứ nhất dựa vào nguyên lý nén
• Nén không tổn hao (Lossless compression):
Gồm các phương pháp nén mà sau khi giải nén ta thu được chính xác dữ liệu
gốc. Tuy nhiên, nén không tổn hao chỉ đạt hiệu quả nhỏ so với phương pháp nén tổn
hao. Các thuật toán nén không tổn hao được dùng để nén các file như: file thực thi,
file văn bản, word, excel,… Các loại dữ liệu này không thể sai lệch dù chỉ một bit,
nhất là các file chương trình.
Các thuật toán nén không tổn hao cơ bản:
1. Run – length encoding (RLE): Phương pháp nén ảnh dựa trên sự cắt bớt
các dư thừa về không gian (một vài hình ảnh có vùng màu lớn không đổi
đặc biệt là ảnh nhị phân)
2. Dictionary coders
3. LZ – 77 & LZ – 78
4. LZW
5. Burrows and Wheeler transform (BWT)
6. Prediction by partial matching (PPM)
7. Context mixing (CM)
8. Entropy encoding
9. Huffman coding
10.Adaptive Huffman
11.Arithematic
12.Shannon – Fano coding
13.Range coding
• Nén tổn hao (Lossy compression):
Gồm các phương pháp nén mà sau khi giải nén ta không thu được dữ liệu như
bản gốc. Đối với tín hiệu âm thanh, hình ảnh, video, kiểu nén tổn hao thường được
sử dụng. Âm thanh và video có thể nén xuống còn 5% so với kích thước gốc, tai và
mắt người khó có thể phát hiện được sự thất thoát dữ liệu này. Trong nén ảnh,
người ta gọi là các phương pháp “tâm lý thị giác”. Các phương pháp này lợi dụng
tính chất của mắt người, chấp nhận một số vặn xoắn trong ảnh khi khôi phục lại.
Tuy nhiên, các phương pháp này chỉ có hiệu quả khi mà độ vặn xoắn là chấp
nhận được bằng mắt thường hay với dung sai nào đó.
"
Trong thực tế, các file hình ảnh, âm thanh hay video được lưu trữ trên máy tính
đều đã được nén có tổn hao để tiết kiệm dung lượng và băng thông. Đối lập với nén
không tổn hao, các phương pháp nén có tổn hao thường gây giảm chất lượng rất
nhanh khi thực hiện nén và giải nén đệ qui nhiều lần. Các mẫu hình ảnh, âm thanh
sẽ được chia thành các phần nhỏ và được biến đổi qua miền khác. Các hệ số biến
đổi này sẽ được lượng tử hóa sau đó được mã hóa bằng mã Huffman hoặc mã hóa
số học.
Các mẫu hình ảnh, âm thanh trước được sử dụng để dự đoán các mẫu tiếp theo.
Sai số giữa dữ liệu dự đoán và dữ liệu thực sẽ được lượng tử hóa rồi mã hóa. Ưu
điểm của nén tổn hao so với nén không tổn hao là nén tổn hao trong nhiều trường
hợp cho tỷ lệ nén cao hơn rất nhiều so với bất cứ thuật toán nén không tổn hao được
biết, trong khi vẫn đảm bảo được chất lượng. Nén tổn hao thường được sử dụng để
nén ảnh, âm thanh, video. Âm thanh có thể nén với tỷ lệ 10:1 mà hầu như không
giảm chất lượng. Video có thể nén với tỷ lệ 300:1 với chất lượng giảm ít.
Các chuẩn nén tổn hao:
1. JPEG (Joint Photographic Experts Group): Phương pháp nén ảnh tĩnh với tỷ lệ
nén lên đến 20 -1 mà không làm mất thông tin đáng kể.
2. LZW (Lempel – Ziv - Welch): Giải thuật này được dùng trong nhiều định
dạng nén, kể cả định dạng có thể trao đổi đồ họa và Tag Image File Format mà
ta thường gọi là TIFF.
3. MPEG (Moving Pictures Experts Group): Phương pháp nén tổn hao dùng cho
video. MPEG -1 dùng cho CD – ROM và video CD. MPEG -2 nén video cho
truyền hình thông thường và độ nét cao.
4. MP3 (MPEG Audio Layer 3): Công nghệ nén âm thanh sử dụng một phần
chuẩn MPEG – 1 để nén âm thanh chất lượng CD với hệ số 12.
5. Fractal: Phương pháp nén tổn hao dữ liệu cho ảnh màu, rất phù hợp với những
đối tượng tự nhiên, tỷ lệ nén có thể lên đến 100 – 1.
6. Wavelet: Phương pháp nén tổn hao sử dụng hàm toán học để nén hình ảnh với
tỷ lệ lớn hơn các phương pháp khác, có thể đạt ¼ kích thước của cùng bức ảnh
nén bằng JPEG.
7. Window Media Technology: Sản phẩm của Microsoft, cho chất lượng âm
thanh tốt hơn MP3 đối với tập tin cùng kích thước, cũng như chất lượng video
gần bằng DVD.
2.1.2. Cách thứ hai dựa vào cách thức thực hiện nén
A
• Phương pháp không gian (Spatial Data Compression): các phương pháp thuộc họ
này thực hiện nén bằng cách tác động trực tiếp lên việc lấy mẫu của ảnh trong
miền không gian.
• Phương pháp sử dụng biến đổi (Transform Coding): các phương pháp tác động
lên sự biến đổi của ảnh gốc mà không tác động trực tiếp như cách phân loại ở
trên.
2.1.3. Cách thứ ba dựa vào lý thuyết mã hóa
• Các phương pháp nén thế hệ thứ nhất: gồm các phương pháp mà mức độ tính
toán là đơn giản như việc lấy mẫu, gán từ mã
• Các phương pháp nén thế hệ thứ hai: gồm các phương pháp dựa vào mức độ bão
hòa của tỷ lệ nén bằng cách sử dụng các phép toán tổ hợp đầu ra một cách hợp lý
hoặc sử dụng biểu diễn ảnh như: phương pháp kim tự tháp Laplace, phương
pháp dựa vào vùng gia tăng, phương pháp tách hợp.
2.2. Một số phương pháp nén
2.2.1. Phương pháp mã hóa Shannon – Fano
Vào những năm 1940, những năm đầu phát triển của ngành Lý thuyết thông tin,
ý tưởng phát triển một phương pháp mới để mã hóa dữ liệu đã bắt đầu xuất hiện, sau
khi các nhà nghiên cứu đã khám phá ra Entropy và độ dư thừa dữ liệu.
Phương pháp mã hóa đầu tiên được nhiều người biết đến vào cuối những năm
1940 là phương pháp mã hóa Shannon – Fano. Phương pháp này được 2 nhà nghiên
cứu Claude Shnnon và R.M Fano đưa ra gần như đồng thời. Kỹ thuật mã hóa này dựa
trên tần suất xuất hiện của mỗi ký tự trong nguồn số liệu cần được mã hóa. Từ bảng
chứa các tần suất đó, bảng mã sẽ được xây dựng dựa vào các tính chất quan trọng sau:
- Các mã khác nhau có các bít biểu diễn khác nhau.
- Ký tự có tần suất xuất hiện càng cao thì mã càng ngắn (ít bít) và ngược lại.
- Các mã có độ dài bit khác nhau.
• Thuật toán:
Các bước thực hiện mã hóa theo thuật toán Shannon – Fano:
- Bước 1: Thiết lập bảng sắp xếp các ký tự theo thứ tự giảm dần.
- Bước 2: Tính xác suất.
- Bước 3: Đệ quy làm 2 phần, mỗi phần có tổng xác suất gần bằng nhau. Mã
hóa phần trên bằng bit 0 ( hoặc bit 1), phần dưới bằng bit 1 ( hoặc bit 0).
- Bước 4: Vẽ sơ đồ cây.
- Bước 5: Tính Entropy, số bít mã hóa trung bình và số bít mã hóa thông
thường.
• Ví dụ:
Thống kê lượng tin:
Ký hiệu A B C D E
Số lần xuất hiện 15 7 6 5 6
Mã hóa lượng tin:
Ký hiệu Đếm P
i
Log
2
(1/p
i
) Mã Tổng bít
A 15 15/39 1.38 0 0 30
B 7 7/39 2.48 0 1 14
C 6 6/39 2.7 1 0 12
D 6 6/39 2.7 1 1 0 18
E 5 5/39 2.96 1 1 1 15
Số bít sử dụng trung bình: (tổng bít/ số lần xuất hiện).
R = (30 + 14 + 12 + 18 + 15) / 39 = 2.29 bít
• Ưu nhược điểm:
- Ưu điểm:
Đơn giản, dễ thực hiện.
- Nhược điểm:
Thuật toán Shannon – Fano có hệ số nén khá thấp và yêu cầu khá phức tạp
nên hiếm khi được sử dụng.
2.2.2. Phương pháp mã hóa Huffman
• Nguyên tắc
Phương pháp mã hóa Huffman là phương pháp dựa trên mô hình thống kê. Dựa
vào dữ liệu gốc, người ta tính tần suất xuất hiện của các ký tự. Việc tính tần suất
được thực hiện bằng cách duyệt tuần tự tệp gốc từ đầu đến cuối. Việc xử lý ở đây
tính theo bit. Trong phương pháp này, người ta gán cho các ký tự có tần suất cao
một từ mã ngắn, các ký tự có tần suất thâp một từ mã dài. Nói cách khác, các ký tự
có tần suất càng cao được gán mã càng ngắn và ngược lại. Rõ ràng với cách thức
này, ta đã làm giảm chiều dài trung bình của từ mã bằng cách dùng chiều dài biến
đổi. Tuy nhiên, trong một số tình huống khi tần suất là rất thấp, ta có thể không thu
được lợi một chút nào, thậm chí còn bị thiệt một ít bit.
• Thuật toán nén
Thuật toán gồm 2 bước chính:
- Bước 1: tính tần suất của các ký tự trong dữ liệu gốc
Duyệt tệp gốc một cách tuần tự từ đầu đến cuối để xây dựng bảng mã. Tiếp
sau đó là sắp xếp lại bảng mã theo thứ tự tần suất giảm dần.
- Bước 2: mã hóa
Duyệt bảng tần suất từ cuối lên đầu để thực hiện ghép 2 phần tử có tần suất
thấp nhất thành một phần tử duy nhất. Phần tử này có tần suất bằng tổng 2 tần
suất thành phần. Tiến hành cập nhật lại bảng và đương nhiên loại bỏ 2 phần
tử đã xét. Quá trình được lặp lại cho đến khi bảng chỉ còn 1 phần tử. Quá
trình này gọi là quá trình tạo cây mã Huffman vì việc tập hợp được tiến hành
nhơ một cây nhị phân với 2 nhánh. Phần tử có tần suất thấp ở bên phải, phần
tử kia ở bên trái. Với cách tạo cây này, tất cả các bit dữ liệu/ ký tự là nút lá,
các nút trong là các nút tổng hợp. Sau khi cây đã tạo xong, người ta tiến hành
gán mã cho các nút lá. Việc mã hóa rất đơn giản: mỗi lần xuống bên phải ta
thêm 1 bit “1” vào từ mã, mỗi lần xuống bên trái ta thêm 1 bit “0”. Tất nhiên
có thể làm ngược lại, chỉ có giá trị mã thay đổi còn tổng chiều dài là không
đổi. Cũng chính vì lý do này mà cây có tên gọi là cây mã Huffman.
Quá trình giải nén tiến hành theo chiều ngược lại khá đơn giản. Người ta cũng
phải dựa vào bảng mã tạo ra trong giai đoạn nén.
• Ví dụ
Một tệp dữ liệu mà tần suất các ký tự cho bởi:
Ký tự Tần suất
“1” 152
“2” 323
“3” 412
“4” 226
“5” 385
“6” 602
“7” 92
“8” 112
“9” 87
“0” 1532
“.” 536
“+” 220
“-” 315
“ ” 535
Bảng tần suất sắp xếp theo thứ tự giảm dần:
Ký tự Tần suất Xác suất
“0” 1532 0.2770
“6” 602 0.1088
“.” 536 0.0969
“ ” 535 0.0967
“3” 412 0.0746
“5” 385 0.0696
“2” 323 0.0585
“-” 315 0.0569
“4” 226 0.0409
“+” 220 0.0396
“1” 152 0.0275
“8” 112 0.0203
“7” 92 0.0167
“9” 87 0.0158
Lưu ý rằng, trong phương pháp Huffman, mã của ký tự là duy nhất và không mã
nào là phần bắt đầu của mã khác. Vì vậy, khi đọc tệp nén từng bit từ đầu đến cuối ta có
thể duyệt cây mã cho đến một lá, tức là ký tự đã được giải nén.
Cây mã Huffman
Bảng từ mã gán cho các ký tự bởi mã hóa Huffman:
Ký tự Từ mã
“0” 10
“6” 010
“.” 001
“ ” 000
“3” 1110
“5” 1100
“2” 0111
“-” 0110
“4” 11110
“+” 11011
“1” 111111
“8” 111110
“7” 110101
“9” 110100
• Thuật toán giải nén
- Bước 1: Đọc lần lượt từng bit trong tập tin nén và duyệt cây nhị phân đã được
xác định cho đến khi hết một lá. Lấy ký tự ở lá đó ghi ra tệp giải nén.
- Bước 2: Trong khi chưa hết tập tin nén thì thực hiện bước một, ngược lại thì
thực hiện bước 3.
- Kết thúc thuật toán.
• Ưu nhược điểm:
Ưu điểm:
Thuật toán Huffman có ưu điểm là hệ số nén tương đối cao, phương pháp thực
hiện tương đối đơn giản, đòi hỏi ít bộ nhớ, có thể xây dựng dựa trên các mảng bé
hơn 64KB.
Nhược điểm:
- Mã Huffman chỉ thực hiện được khi biết tần suất xuất hiện của các ký tự.
- Mã Huffman chỉ giải quyết được độ dư thừa phân bố ký tự.
- Huffman tĩnh đòi hỏi phải xây dựng cây nhị phân sẵn chứa các khả năng.
Điều này đòi hỏi thời gian không ít do ta không biết trước kiểu dữ liệu sẽ
được thực hiện nén.
- Quá trình giải nén phức tạp do chiều dài mã không biết trước cho đến khi ký
tự đầu tiên được tìm ra.
2.2.3. Phương pháp mã hóa RLE
Mã hóa loạt dài RLE là một phương pháp nén ảnh dựa trên sự cắt bớt các dư thừa
về không gian (một vài hình ảnh có vùng màu lớn không đổi đặc biệt là ảnh nhị phân).
Loạt được định nghĩa là dãy các phần tử điểm ảnh (pixel) liên tiếp có cùng chung một
giá trị. Phương pháp mã hóa loạt dài lúc đầu được phát triển dành cho ảnh nhị phân
như các văn bản trên nền trắng, trang in, các bức vẽ kỹ thuật.
• Nguyên tắc
Phát hiện một loạt các bit lặp lại, thí dụ như một loạt các bit 0 nằm giữa 2 bit 1
hay ngược lại, một loạt bit 1 nằm giữa 2 bit 0. Ví dụ: 110000000000000011. Dãy các
bit lặp gọi là loạt hay mạch (run). Tiếp theo, thay thế chuỗi đó bởi một chuỗi mới gồm
2 thông tin: chiều dài chuỗi và bit lặp (ký tự lặp). Như vậy, chuỗi thay thế sẽ có chiều
dài ngắn hơn chuỗi cần thay.
- Ví dụ:
Cho một chuỗi nguồn d:
d= 5 5 5 5 5 5 5 5 5 5 19 19 19 19 19 0 0 0 0 0 0 0 23 23 23 23 23 23 23 23
Ta sẽ có chuỗi mới là: (10 5) (5 19) (7 0) (8 23)
Tỷ số nén = 30/8 = 3.75
Đối với ảnh đen trắng chỉ sử dụng 1 bit để biểu diễn 1 điểm ảnh thì phương pháp
này tỏ ra rất hiệu quả.
- Ví dụ:
Cho một chuỗi nguồn:
d= 000000000000000111111111100000000001111111111000000000000000
>
Ta có chuỗi mới: (15, 10, 10, 10, 15)
Tỷ số nén = 60/(5*4) = 3 (chỉ sử dụng 4 bit để thể hiện độ dài loạt và không thể
hiện giá trị loạt vì ảnh đen trắng chỉ có 2 giá trị bit là 0 hoặc 1).
• Chú ý:
- Cần lưu ý rằng, đối với dữ liệu ảnh, chiều dài của chuỗi lặp có thể lớn hơn
255, nếu ta dùng một byte để lưu trữ chiều dài thì không đủ. Giải pháp được
dùng là tách chuỗi đó thành 2 chuỗi: một chuỗi có chiều dài 255, chuỗi kia là
số bit còn lại.
- Phương pháp này chỉ có hiệu quả khi chiều dài dãy lặp lớn hơn một ngưỡng
nào đó hay nói cách khác trong ảnh cần nén phải có nhiều điểm ảnh kề nhau
có cùng giá trị màu. Do đó, phương pháp này không đem lại cho ta kết quả
một cách ổn định vì nó phụ thuộc hoàn toàn vào ảnh nén, chỉ thích hợp cho
ảnh đen trắng hay ảnh đa cấp xám.
- Ví dụ:
Ta có một chuỗi nguồn:
d = 5 7 9 11 13 18 28 38 48 58 30 35 40 45
Chuỗi kết quả sau khi mã hóa:
1 5 1 7 1 9 1 11 1 3 1 18 1 28 1 38 1 48 1 58 1 30 1 35 1 40 1 45
Tỷ số nén = 14/ 28 = 0.5
Như vậy, chuỗi sau khi mã hóa lớn hơn nhiều chuỗi nguồn ban đầu. Do đó, cần
phương pháp cải tiến để xử lý những trường hợp như trên tránh làm mở rộng chuỗi dữ
liệu nguồn nghĩa là chỉ mã hóa độ dài loạt dữ liệu lặp lại. Người ta đưa ra cách là thêm
ký tự tiền tố vào trước độ dài loạt, việc giải mã được thực hiện nếu gặp ký tự tiền tố
với độ dài loạt và giá trị điểm ảnh theo sau.
- Ví dụ:
Ta có chuỗi nguồn:
d= 5 8 4 8 8 8 8 8 8 8 8 10 10 10 10 10 10 10 10 10
Giả sử ký tự tiền tố là “@” ta có chuỗi sau khi mã hóa: 5 8 4 @ 7 8 @ 9 10
Tỷ số nén = 19/9/ 2.1
Tuy nhiên, trong một số trường hợp các điểm ảnh có độ tương quan với nhau về
giá trị mức xám như trong ví dụ dưới đây ta có thể tiến hành xử lý như sau:
- Ví dụ:
Ta có một chuỗi nguồn:
?
d= 5 7 9 11 13 18 28 38 48 58 55 60 65 70 75 80 85 90 95 100
Ta dựa vào độ tương quan này để có được hiệu quả nén cao, bằng việc áp dụng
e(i) = d(i) – d(i-1) sẽ thu được:
5 2 2 2 2 5 10 10 10 10 10 -3 5 5 5 5 5 5 5 5 5
Áp dụng phương pháp nén RLE ta thu được:
(1 5)(4 2)(1 5)(5 10)(1 -3)(9 5)
2.2.4. Phương pháp mã hóa LZ78
• Mô hình từ điển (Dictionary – based compression):
Có 2 loại:
- Mã hóa từ điển tĩnh ( Static dictionary coding)
- Mã hóa từ điển động ( Dynamic dictionary coding)
Có rất nhiều thuật toán áp dụng kỹ thuật này như: LZ77, LZK, LZSS, LZH…
nhưng trong bài này chúng ta chỉ đề cập đến hai thuật toán chính là:
- Thuật toán LZ78
- Thuật toán LZW
Jacob Ziv và Abraham Lempel đã mô tả kỹ thuật dựa trên từ điển bằng mã hóa
LZ77 và LZ78. Ý tưởng dựa trên việc thay thế 1 cụm ký tự bằng một con trỏ, trỏ đến
vị trí xuất hiện trước đó của cụm ký tự.
LZW là mã hóa trong họ LZ, hoàn thiện hơn LZ77 – LZ78 và đang được sử dụng
phổ biến hiện nay.
• Nguyên tắc:
Thay vì thông báo vị trí đoạn văn lặp lại trong quá khứ, mã LZ78 đánh số tất cả
các đoạn văn sao cho mỗi đoạn ghi nhận số hiệu đoạn văn lặp lại trong quá khứ
cộng với một ký tự mà nó làm cho đoạn đó khác với đoạn trong quá khứ. Như vậy,
mỗi đoạn mới là một đoạn ký tự trong quá khứ cộng với một ký tự trong quá khứ.
Chính vì thế đoạn mới khác với đoạn cũ trong quá khứ.
• Ví dụ:
Giả sử ta có đoạn văn bản sau: “aaabbabaabaaabab”
Theo thuật toán LZ78 thì chúng được phân thành các đoạn như sau:
Input a aa b ba baa baaa bab
Đoạn 1 2 3 4 5 6 7
Output 0 + a 1 + a 0 + b 3 + a 4 + a 5 + a 4 + b
Như vậy, bản nén của chúng ta là: (0, a); (1, a); (0, b); (3, a); (4, a); (5, a); (4, b)
9
Nói chung thuật toán LZ78 là một thuật toán nén vă bản khá tốt, có thời gian
chạy chương trình tương đối nhanh, tuy nhiên khả năng tiết kiệm chưa được khai thác
tối đa.
2.2.5. Phương pháp mã hóa LZW
Khái niệm nén từ điển được Jocob Lempe và Abraham Ziv đưa ra lần đầu tiên
năm 1977 và lấy tên là LZ77. Năm 1978 cải tiến dựa trên LZ77 và lấy tên là LZ78.
Năm 1984, Welch đã cải tiến giải thuật LZ thành giải thuật mới hiệu quả hơn và được
đặt tên là LZW (Lempe – Ziv - Welch). Phương pháp này xây dựng từ điển lưu các
chuỗi ký tự có tần suất lặp lại cao và thay thế bằng từ mã tương ứng mỗi khi gặp lại
chúng, nó hay hơn các phương pháp trước đó ở kỹ thuật tổ chức từ điển cho phép nâng
cao tỷ lệ nén.
Giải thuật LZW được dùng cho tất cả các loại file nhị phân, thường được dùng để
nén các loại dữ liệu như: văn bản, ảnh đen trắng, ảnh màu, ảnh đa cấp xám… và là
chuẩn nén cho các dạng ảnh GIF và TIFF. Số bit/pixel không ảnh hưởng đến hiệu quả
của LZW.
• Nguyên tắc
Giải thuật nén LZW xây dựng một từ điển lưu các mẫu có tần suất xuất hiện cao
trong ảnh. Từ điển là tập hợp những cặp từ vựng và nghĩa của từ vựng. Trong đó, từ
vựng sẽ là các từ mã được sắp xếp theo thứ tự nhất định, nghĩa là một chuỗi con trong
dữ liệu ảnh, từ điển được xây dựng song song với quá trình đọc dữ liệu. Sự xuất hiện
của chuỗi con trong từ điển khẳng định rằng chuỗi đó đã từng xuất hiện trong phần dữ
liệu đã được đọc qua.Thuật toán liên tục tra cứu và sau mỗi lần đọc một ký tự ở dữ liệu
đầu vào thì tiến hành cập nhật lại từ điển.
Do giới hạn của bộ nhớ và để đảm bảo tốc độ tìm kiếm nhanh, từ điển chỉ có giới
hạn 4096 phần tử dùng để lưu trữ giá trị của các từ mã. Như vậy, độ dài lớn nhất của
từ mã là 12 bit (4096 = ). Cấu trúc từ điển như sau:
@
- 256 từ mã đầu tiên theo thứ tự từ 0…255 chứa các số nguyên từ 0…255. Đây là
mã của 256 ký tự cơ bản trong bảng mã ASCII.
- Từ mã 256 chứa một mã đặc biệt là mã xóa (CC – Clear Code). Khi số mẫu lặp
lớn hơn 4096 thì người ta sẽ coi ảnh gồm nhiều mảnh ảnh và từ điển sẽ gồm
nhiều từ điển con. Khi hết một mảnh ảnh sẽ gửi 1 mã xóa CC để báo hiệu kết
thúc mảnh ảnh cũ và bắt đầu mảnh ảnh mới đồng thời sẽ khởi tạo lại từ điển.
- Từ mã thứ 257 chứa mã kết thúc thông tin (EOI – End Of Information). Thông
thường một file ảnh GIF có thể chứa nhiều mảnh ảnh, mỗi mảnh ảnh này sẽ được
mã hóa riêng. Chương trình giải mã sẽ lặp đi lặp lại thao tác giải mã từng ảnh cho
đến khi gặp mã kết thúc thông tin thì dừng lại.
- Các từ mã còn lại (từ 258 đến 4095) chứa các mẫu thường lặp lại trong ảnh. 512
phần tử đầu tiên của từ điển biểu diễn bằng 9 bit. Các từ mã từ 512 đến 1023 biểu
diễn bởi 10 bit, từ 1024 đến 2047 biểu diễn bởi 11 bit và từ 2048 đến 4095 biểu
diễn bởi 12 bit.
• Ví dụ
Cho chuỗi đầu vào: “HELLOHELLOHELL”
Từ điển ban đầu đã gồm 256 ký tự cơ bản.
Kích thước đầu vào: 14 x 8 = 112 bit
Đầu vào Đầu ra Thực hiện
H(72) H đã có trong từ điển -> đọc tiếp
E(69) 72 Thêm vào từ điển mã 258 đại diện chuỗi HE
L(76) 69 Thêm vào từ điển mã 259 đại diện chuỗi EL
"
L 76 Thêm vào từ điển mã 260 đại diện chuỗi LL
O(79) 76 Thêm vào từ điển mã 261 đại diện chuỗi LO
H 79 Thêm vào từ điển mã 262 đại diện chuỗi OH
E HE đã có trong từ điển -> đọc tiếp
L 258 Thêm vào từ điển mã 263 đại diện chuỗi HEL
L LL đã có trong từ điển -> đọc tiếp
O 260 Thêm vào từ điển mã 264 đại diện chuỗi LLO
H OH đã có trong từ điển -> đọc tiếp
E 262 Thêm vào từ điển mã 265 đại diện chuỗi OHE
L EL đã có trong từ điển -> đọc tiếp
L 259 Thêm vào từ điển mã 266 đại diện chuỗi ELL
76 Input = FALSE
EOI
Chuỗi đầu ra là: 72 69 76 76 79 258 260 262 259 76
Kích thước đầu ra: 6 x 8 + 4 x 9 = 84 bit
Tỷ số nén = 112/ 84 = 1.3
Quá trình giải nén thực hiện như sau:
Code Outbuff()
AddToDictionary()
CodeWord String
72 H
69 E 258 HE
76 L 259 EL
76 L 260 LL
79 O 261 LO
258 HE 262 OHE
260 LL 263 HEL
262 OH 264 LLO
259 EL 265 OHE
76 L 266 ELL
EOI
Chuỗi thu được sau giải nén: “HELLOHELLOHELL”
2.3. Chọn phương pháp nén
Qua ba phương pháp nén được dùng phổ biến trên, ta thấy rằng, thuật toán nén
độ dài loạt (Runlength) không thể áp dụng cho mhiều loại tập tin được, ví dụ như tập
A
tin chương trình, tập tin cơ sở dữ liệu vì ở đó các loạt chạy là rất ngắn, do đó nếu áp
dụng thuật toán này không những không làm bé tập tin mà còn làm phình to chúng.
Hai thuật toán còn lại (Huffman và LZW) đều có thể áp dụng được để nén nhiều loại
tập tin trên các máy vi tính.
Thuật toán Huffman có ưu điểm là hệ số nén tương đối cao, phương pháp thực
hiện tương đối đơn giản, đòi hỏi ít bộ nhớ, có thể xây dựng dựa trên các mảng bé hơn
64KB. Nhược điểm của nó là phải chứa cả bảng mã vào tập tin nén thì phía nhận mới
có thể giải mã được do đó hiệu suất nén chỉ cao khi ta thực hiện nén các tập tin lớn.
Thuật toán nén LZW có các ưu điểm là hệ số nén tương đối cao, trong tập tin nén
không cần phải chứa bảng mã. Nhược điểm của thuật toán này là tốn nhiều bộ nhớ,
khó thực hiện dựa trên các mảng đơn giản (bé hơn 64KB).
Từ các ưu và nhược điểm trên, ta chọn phương pháp nén Huffman vì tính đơn
giản của nó hệ số nén lại cao. Nhược điểm của phương pháp nén này có thể khắc phục
bằng cách thực hiện nén một lần nhiều tập tin chuẩn bị truyền, làm như vậy coi như
chúng ta đang thực hiện nén một tập tin lớn.
CHƯƠNG 3: CHƯƠNG TRÌNH ỨNG DỤNG
3.1. Giao diện chương trình
Chương trình mô phỏng nén dữ liệu dựa trên thuật toán nén Huffman. Một số
chức năng chính của chương trình:
- Mở tệp nguồn: có chức năng mở 1 tệp nguồn cần giải nén
- Xuất tệp nén: có chức năng lưu tệp đã được nén
- Giải nén tệp: có chức năng giải nén 1 tệp
- Nén tệp: có chức năng nén 1 tệp
Hình ảnh minh họa chương trình nén
3.2. Một số thủ tục chính trong chương trình
3.2.1. Thủ tục xây dựng cây Huffman
class HuffmanTree
{
#region Members
/// <summary>
/// mảng lưu giá trị của byte
/// </summary>
public readonly TreeNode[] Leafs;
public readonly FrequencyTable FT;
/// <summary>
/// lưu trữ các node không có nút cha
/// </summary>
private ArrayList OrphanNodes= new ArrayList();
/// <summary>
/// nút gốc trong cây
/// </summary>
public readonly TreeNode RootNode;
#endregion
/// <summary>Xây dựng cây Huffman.</summary>
internal HuffmanTree(FrequencyTable FT)
{
ushort Length = (ushort)FT.FoundBytes.Length;
this.FT= FT;
Leafs = new TreeNode[Length];
if( Length > 1 )
{
for(ushort i=0; i< Length; ++i)
{
Leafs[i]=new TreeNode();
Leafs[i].ByteValue = FT.FoundBytes[i];
Leafs[i].Value = FT.Frequency[i];
}
OrphanNodes.AddRange(Leafs);
RootNode = BuildTree();
}
else
TreeNode TempNode = new TreeNode();
TempNode.ByteValue =FT.FoundBytes[0];
TempNode.Value = FT.Frequency[0];
RootNode = new TreeNode();
RootNode.Lson = RootNode.Rson =TempNode;
}
OrphanNodes.Clear();
OrphanNodes=null;
}
//
/// <summary>
/// Hàm này xây dựng cây từ bảng tần suất
/// </summary>
private TreeNode BuildTree()
{
TreeNode small, smaller, NewParentNode=null;
/* ( only one root )*/
while( OrphanNodes.Count > 1 )
{
FindSmallestOrphanNodes(out smaller,out small);
NewParentNode = new TreeNode();
NewParentNode.Value = small.Value + smaller.Value;
NewParentNode.Lson = smaller;
NewParentNode.Rson = small;
smaller.Parent = small.Parent = NewParentNode;
OrphanNodes.Add(NewParentNode);
}
// Trả về gốc của cây
return NewParentNode;
}
//
/// <summary>
/// Tìm 2 nút có giá trị nhỏ nhất sau đó gỡ khỏi mảng
/// </summary>
private void FindSmallestOrphanNodes(out TreeNode Smallest, out
TreeNode Small)
{
Smallest = Small = null;
ulong Tempvalue=18446744073709551614;
TreeNode TempNode=null;
int i, j=0;
int ArrSize = OrphanNodes.Count-1;
for(i= ArrSize ; i!=-1 ; i )
{
TempNode = (TreeNode)OrphanNodes[i];
if( TempNode.Value < Tempvalue )
{
Tempvalue = TempNode.Value;
Smallest = TempNode;
j=i;
}
}
OrphanNodes.RemoveAt(j);
ArrSize;
Tempvalue=18446744073709551614;
for(i= ArrSize; i>-1 ; i )
{
TempNode = (TreeNode)OrphanNodes[i];
if( TempNode.Value < Tempvalue )
{
Tempvalue = TempNode.Value;
Small = TempNode;
j=i;
}
}
OrphanNodes.RemoveAt(j);
}
//
}//end of HuffmanTree class
3.2.2. Thủ tục nén dữ liệu
public Stream Shrink(Stream Data, char[] Password)
{
String TempDir=Environment.GetEnvironmentVariable("temp");
HuffmanTree HT = new HuffmanTree( BuildFrequencyTable(Data) );
FileStream tempFS= new FileStream( TempDir + @"\TempArch.tmp",
FileMode.Create);
WriteHeader(tempFS, HT.FT, Data.Length, 11,
GetComplementsBits(HT), Password );
long DataSize= Data.Length;
TreeNode TempNode=null;
Byte Original; //byte dữ liệu cần đọc
short j; int k;
for(long i=0;i< DataSize; ++i)
{
Original = (Byte)Data.ReadByte();
TempNode = HT.Leafs[ ByteLocation[Original] ];
while( TempNode.Parent!=null )
{
BitsList.Add(TempNode.Parent.Lson == TempNode);
TempNode = TempNode.Parent;
}
BitsList.Reverse();
k = BitsList.Count;
for(j=0; j<k; ++j )
{
Stack.PushFlag( (bool)BitsList[j] );
if(Stack.IsFull())
{
tempFS.WriteByte(Stack.Container);
Stack.Empty();
}
}
BitsList.Clear();
}
if( !Stack.IsEmpty() )
{
Byte BitsToComplete = (Byte)(8 - Stack.NumOfBits());
for(byte Count=0; Count< BitsToComplete; ++Count)//đủ 8 bit
Stack.PushFlag(false);
tempFS.WriteByte(Stack.Container);
Stack.Empty();
}
tempFS.Seek( 0, SeekOrigin.Begin );
return tempFS;
}
//
/// Xây dựng bảng tần suất, cây Huffman và nén luồng dữ liệu
public Stream ShrinkWithProgress(Stream Data, char[] Password)
{
String TempDir=Environment.GetEnvironmentVariable("temp");
HuffmanTree HT = new HuffmanTree( BuildFrequencyTable(Data) );
FileStream tempFS= new FileStream( TempDir + @"\TempArch.tmp",
FileMode.Create);
WriteHeader(tempFS, HT.FT, Data.Length, 11,
GetComplementsBits(HT), Password );
long DataSize= Data.Length;
TreeNode TempNode=null;
Byte Original; //các bytes cần đọc từ luồng dữ liệu
short j; int k; float ProgressRatio = 0;
for(long i=0;i< DataSize; ++i)
{
Original = (Byte)Data.ReadByte();
TempNode = HT.Leafs[ ByteLocation[Original] ];
while( TempNode.Parent!=null )
{
BitsList.Add(TempNode.Parent.Lson == TempNode);
TempNode = TempNode.Parent;//leo cây.
}
BitsList.Reverse();
k = BitsList.Count;
for(j=0; j<k; ++j )
{
Stack.PushFlag( (bool)BitsList[j] );
if( Stack.IsFull() )
{
tempFS.WriteByte(Stack.Container);
Stack.Empty();
}
}
BitsList.Clear();
if( ((float)(i))/DataSize - ProgressRatio > 0.01 )
{
ProgressRatio+=0.01f;
if( OnPercentCompleted!=null ) OnPercentCompleted();
}
}
//Ghi các byte trước nếu stack không đủ
if( !Stack.IsEmpty() )
{
Byte BitsToComplete = (Byte)(8 - Stack.NumOfBits());
>