Jfif sang jpg Python

Một trong những lợi thế của việc trở thành một lập trình viên là khả năng xây dựng các công cụ tiện ích để cải thiện cuộc sống của bạn. Không giống như một người không phải là lập trình viên, bạn có thể không dành hàng giờ để tìm kiếm trong nhiều trang kết quả tìm kiếm của Google để tìm một công cụ mà ngay từ đầu được cho là sẽ cải thiện năng suất của bạn [chiến thắng trớ trêu]. Điều này có thể khiến bạn cảm thấy mạnh mẽ hơn khi biết một ngôn ngữ lập trình — đặc biệt nếu ngôn ngữ Lập trình đó linh hoạt và tuyệt vời như Python.

Một trong những điểm trong nói

Đơn giản là tốt hơn phức tạp

Với triết lý này, rất nhiều công cụ phát triển thích hợp sử dụng Python có thể được thực hiện ngắn gọn đến mức khiến tôi tự hỏi liệu có đáng để gọi nó là một công cụ hay không. Đôi khi từ

import glob
31 sẽ chính xác hơn. Dù bằng cách nào, chúng tôi đang bắt đầu xây dựng một
import glob
31 như vậy để chuyển đổi hình ảnh từ một định dạng tệp [loại hình ảnh] sang một định dạng tệp khác — chỉ trong 6 dòng mã Python

từ chối trách nhiệm. Số dòng [6] không kể dòng trống và chú thích

Trong hướng dẫn này, chúng ta sẽ xây dựng một trình chuyển đổi loại hình ảnh để chuyển đổi hình ảnh PNG thành hình ảnh JPG. Trước khi các tế bào chất xám của bạn đổ xô phán xét xem tôi có điên không khi xây dựng công cụ này, hãy để tôi nói rằng đây không chỉ dành cho một hình ảnh — mà còn cho tất cả các hình ảnh trong một thư mục. Điều đó chắc chắn sẽ đòi hỏi nhiều nỗ lực thủ công hơn để thực hiện mà không cần viết mã [Tôi biết bạn có thể ngửi thấy mùi

import glob
33]

Gói Python

Chúng tôi sẽ sử dụng gói Python

import glob
0 [viết tắt của Thư viện hình ảnh Python] cho mục đích này. Phiên bản gốc
import glob
0 không nhận được bất kỳ bản cập nhật nào cho phiên bản Python mới nhất, vì vậy một số người tốt bụng đã tạo ra một nhánh rẽ thân thiện có tên là
import glob
2 hỗ trợ cả > Python 3. 0

Cài đặt nó bằng cách sử dụng

import glob
3

Kịch bản bắt đầu

Có hai phần chính trong mã này. Phần đầu tiên là nơi chúng tôi nhập các gói cần thiết và phần thứ hai là nơi hoạt động thực tế diễn ra. Hoạt động thực tế có thể được chia nhỏ hơn nữa như sau

  • Lặp lại tất cả các tệp có phần mở rộng đã cho — trong trường hợp của chúng tôi là
    import glob
    4 — và lặp lại tất cả các thao tác sau
  • Mở tệp hình ảnh [dưới dạng tệp hình ảnh]
  • Chuyển đổi tệp hình ảnh sang định dạng khác [
    import glob
    5 ]
  • Cuối cùng lưu tệp — với phần mở rộng mới là
    import glob
    6

Dòng 1 và 2

from PIL import Image  # Python Image Library - Image Processing
import glob

Phần này chỉ cần nhập các gói cần thiết.

import glob
0 để Xử lý hình ảnh và
import glob
8 để lặp qua các tệp của thư mục đã cho trong HĐH

Dòng 3–6

import glob
1
import glob
2
import glob
3
import glob
4
import glob
5

VÒNG

Vì vậy, đó là kết thúc của công cụ của chúng tôi. Bạn có thể lưu 6 dòng này dưới dạng tệp

import glob
9 và sau đó gọi chúng trong máy tính nơi bạn có hình ảnh để chuyển đổi

Phát triển hơn nữa

Nếu bạn đang có kế hoạch cải thiện tập lệnh này hơn nữa, bạn có thể chuyển đổi toàn bộ tập lệnh này thành Công cụ Giao diện Dòng Lệnh — sau đó, tất cả các chi tiết như

import glob
10 và
import glob
11 có thể được đưa ra làm đối số để mở rộng sức mạnh của nó hơn nữa

Người giới thiệu

  • Mã hoàn chỉnh được sử dụng ở đây có sẵn trên github của tôi
  • Cái gối

QUẢNG CÁO

QUẢNG CÁO

QUẢNG CÁO

Nếu bài viết này hữu ích, hãy tweet nó

Học cách viết mã miễn phí. Chương trình giảng dạy mã nguồn mở của freeCodeCamp đã giúp hơn 40.000 người có được việc làm với tư cách là nhà phát triển. Bắt đầu

chào mọi người. 👋 Hôm nay chúng ta sẽ tìm hiểu thuật toán nén JPEG. Một điều mà nhiều người không biết là JPEG không phải là một định dạng mà là một thuật toán. Các hình ảnh JPEG mà bạn thấy hầu hết ở định dạng JFIF [Định dạng trao đổi tệp JPEG] sử dụng bên trong thuật toán nén JPEG. Đến cuối bài viết này, bạn sẽ hiểu rõ hơn về cách thuật toán JPEG nén dữ liệu và cách bạn có thể viết một số mã Python tùy chỉnh để giải nén nó. Chúng tôi sẽ không đề cập đến tất cả các sắc thái của định dạng JPEG [như quét lũy tiến] mà chỉ đề cập đến định dạng đường cơ sở cơ bản trong khi viết bộ giải mã của chúng tôi

Giới thiệu

Tại sao lại viết một bài viết khác về JPEG khi đã có hàng trăm bài báo trên internet? . Bạn không triển khai bất kỳ mã nào để thực hiện giải nén và giải mã thực tế. Ngay cả khi bạn viết mã, nó vẫn ở dạng C/C++ và nhiều người không thể truy cập được. Tôi dự định thay đổi điều đó bằng cách chỉ cho bạn cách bộ giải mã JPEG cơ bản hoạt động bằng Python 3. Tôi sẽ dựa trên bộ giải mã của mình dựa trên mã được cấp phép MIT này nhưng sẽ sửa đổi nó rất nhiều để tăng khả năng đọc và dễ hiểu. Bạn có thể tìm thấy mã sửa đổi cho bài viết này trên repo GitHub của tôi

Các phần khác nhau của JPEG

Hãy bắt đầu với hình ảnh đẹp này của Ange Albertini. Nó liệt kê tất cả các phần khác nhau của một tệp JPEG đơn giản. Hãy nhìn vào nó. Chúng ta sẽ khám phá từng phân khúc. Bạn có thể phải tham khảo hình ảnh này khá nhiều lần khi đọc hướng dẫn này

Ở cấp độ rất cơ bản, hầu hết mọi tệp nhị phân đều chứa một vài điểm đánh dấu [hoặc tiêu đề]. Bạn có thể coi những điểm đánh dấu này giống như những dấu trang. Chúng rất quan trọng để hiểu ý nghĩa của tệp và được sử dụng bởi các chương trình như

import glob
99 [trên Mac/Linux] để cho chúng tôi biết chi tiết về tệp. Các điểm đánh dấu này xác định nơi lưu trữ một số thông tin cụ thể trong tệp. Hầu hết các điểm đánh dấu được theo sau bởi thông tin
import glob
00 cho đoạn đánh dấu cụ thể. Điều này cho chúng tôi biết đoạn cụ thể đó dài bao lâu

Bắt đầu tệp & Kết thúc tệp

Điểm đánh dấu đầu tiên mà chúng tôi quan tâm là

import glob
01. Nó cho chúng ta biết rằng đây là điểm bắt đầu của hình ảnh. Nếu chúng tôi không nhìn thấy nó, chúng tôi có thể cho rằng đây là một số tệp khác. Một điểm đánh dấu quan trọng không kém khác là
import glob
02. Nó cho chúng tôi biết rằng chúng tôi đã đến cuối tệp hình ảnh. Mọi điểm đánh dấu, ngoại trừ
import glob
03 đến
import glob
04 và
import glob
05, ngay sau đó là một bộ xác định độ dài sẽ cung cấp cho bạn độ dài của đoạn đánh dấu đó. Đối với các điểm đánh dấu bắt đầu và kết thúc tệp hình ảnh, chúng sẽ luôn dài hai byte mỗi dấu

Trong suốt hướng dẫn này, chúng tôi sẽ làm việc với hình ảnh này

Hãy viết một số mã để xác định các điểm đánh dấu này

import glob
6

Chúng tôi đang sử dụng cấu trúc để giải nén byte dữ liệu hình ảnh.

import glob
06 yêu cầu
import glob
07 coi dữ liệu là big-endian và thuộc loại
import glob
08. Dữ liệu trong JPEG được lưu trữ ở định dạng big-endian. Chỉ dữ liệu EXIF ​​​​có thể ở dạng little-endian [mặc dù nó không phổ biến]. Và một đoạn ngắn có kích thước 2, vì vậy chúng tôi cung cấp
import glob
09 hai byte từ
import glob
10 của chúng tôi. Bạn có thể tự hỏi làm thế nào chúng tôi biết đó là một
import glob
11. Chà, chúng tôi biết rằng các điểm đánh dấu trong JPEG là 4 chữ số hex.
import glob
12. Một chữ số hex bằng 4 bit [1⁄2 byte] nên 4 chữ số hex sẽ bằng 2 byte và một chữ số ngắn bằng 2 byte

Phần Bắt đầu quét ngay sau đó là dữ liệu quét hình ảnh và dữ liệu quét hình ảnh đó không có độ dài được chỉ định. Nó tiếp tục cho đến khi tìm thấy điểm đánh dấu “cuối tệp”, vì vậy hiện tại chúng tôi đang “tìm kiếm” điểm đánh dấu EOF theo cách thủ công bất cứ khi nào chúng tôi nhìn thấy điểm đánh dấu SOC

Bây giờ chúng ta đã có khung cơ bản, hãy tiếp tục và tìm hiểu xem phần còn lại của dữ liệu hình ảnh chứa gì. Trước tiên chúng ta sẽ xem qua một số lý thuyết cần thiết và sau đó bắt đầu viết mã

Mã hóa JPEG

Trước tiên tôi sẽ giải thích một số khái niệm cơ bản và kỹ thuật mã hóa được sử dụng bởi JPEG và sau đó giải mã sẽ diễn ra một cách tự nhiên từ đó ngược lại với nó. Theo kinh nghiệm của tôi, việc trực tiếp cố gắng hiểu ý nghĩa của việc giải mã là hơi khó

Mặc dù hình ảnh dưới đây không có nhiều ý nghĩa đối với bạn ngay bây giờ, nhưng nó sẽ cung cấp cho bạn một số mỏ neo để bám vào trong khi chúng tôi thực hiện toàn bộ quá trình mã hóa/giải mã. Nó cho thấy các bước liên quan đến quá trình mã hóa JPEG. [src]

Không gian màu JPEG

Theo thông số JPEG [ISO/IEC 10918-6. 2013 [E], mục 6. 1]

  • Hình ảnh được mã hóa chỉ với một thành phần được coi là dữ liệu thang độ xám trong đó 0 là màu đen và 255 là màu trắng
  • Hình ảnh được mã hóa với ba thành phần được coi là dữ liệu RGB được mã hóa thành YCbCr trừ khi hình ảnh chứa đoạn nhãn APP14 như được chỉ định trong 6. 5. 3, trong trường hợp đó, mã hóa màu được coi là RGB hoặc YCbCr theo dữ liệu ứng dụng của đoạn đánh dấu APP14. Mối quan hệ giữa RGB và YCbCr được xác định như được chỉ định trong Rec. ITU-T T. 871. ISO/IEC 10918-5
  • Hình ảnh được mã hóa với bốn thành phần được coi là CMYK, với [0,0,0,0] biểu thị màu trắng trừ khi hình ảnh chứa đoạn đánh dấu APP14 như được chỉ định trong 6. 5. 3, trong trường hợp đó, mã hóa màu được coi là CMYK hoặc YCCK theo dữ liệu ứng dụng của đoạn đánh dấu APP14. Mối quan hệ giữa CMYK và YCCK được xác định như quy định tại khoản 7

Hầu hết các triển khai thuật toán JPEG đều sử dụng độ chói và sắc độ [mã hóa YUV] thay vì RGB. Điều này cực kỳ hữu ích trong JPEG vì mắt người khá tệ khi nhìn thấy sự thay đổi độ sáng ở tần số cao trên một khu vực nhỏ, vì vậy về cơ bản chúng ta có thể giảm lượng tần số và mắt người sẽ không thể nhận ra sự khác biệt. Kết quả?

Giống như mỗi pixel trong không gian màu RGB được tạo thành từ 3 byte dữ liệu màu [Đỏ, Xanh lục, Xanh lam], mỗi pixel trong YUV cũng sử dụng 3 byte nhưng mỗi byte thể hiện hơi khác một chút. Thành phần Y xác định độ sáng của màu [còn được gọi là độ chói hoặc độ sáng], trong khi thành phần U và V xác định màu [còn được gọi là sắc độ]. Thành phần U đề cập đến lượng màu xanh lam và thành phần V đề cập đến lượng màu đỏ

Định dạng màu này được phát minh khi TV màu không quá phổ biến và các kỹ sư muốn sử dụng một định dạng mã hóa hình ảnh cho cả TV màu và đen trắng. YUV có thể được hiển thị an toàn trên TV đen trắng nếu không có màu. Bạn có thể đọc thêm về lịch sử của nó trên Wikipedia

Biến đổi Cosine rời rạc và lượng tử hóa

JPEG chuyển đổi một hình ảnh thành các khối pixel 8x8 [được gọi là MCU hoặc Đơn vị mã hóa tối thiểu], thay đổi phạm vi giá trị của pixel để chúng tập trung vào 0 và sau đó áp dụng Biến đổi Cosine rời rạc cho từng khối và sau đó sử dụng lượng tử hóa để nén . Hãy hiểu rõ hơn về ý nghĩa của tất cả các thuật ngữ này

Biến đổi Cosine rời rạc là một phương pháp để chuyển đổi các điểm dữ liệu rời rạc thành sự kết hợp của các sóng cosine. Có vẻ khá vô ích khi dành thời gian chuyển đổi một hình ảnh thành một loạt cosin nhưng sẽ hợp lý khi chúng ta hiểu DCT kết hợp với cách hoạt động của bước tiếp theo. Trong JPEG, DCT sẽ lấy một khối hình ảnh 8x8 và cho chúng tôi biết cách tái tạo nó bằng cách sử dụng ma trận hàm cosine 8x8. Đọc thêm tại đây]

Ma trận 8x8 của các hàm cosin trông như thế này

Chúng tôi áp dụng DCT cho từng thành phần của pixel một cách riêng biệt. Đầu ra của việc áp dụng DCT là một ma trận hệ số 8x8 cho chúng ta biết mỗi hàm cosin [trong tổng số 64 hàm] đóng góp bao nhiêu vào ma trận đầu vào 8x8. Ma trận hệ số của DCT thường chứa các giá trị lớn hơn ở góc trên cùng bên trái của ma trận hệ số và các giá trị nhỏ hơn ở góc dưới cùng bên phải. Góc trên cùng bên trái biểu thị hàm cosin tần số thấp nhất và góc dưới bên phải biểu thị hàm cosin tần số cao nhất

Điều này cho chúng ta biết rằng hầu hết các hình ảnh chứa một lượng lớn thông tin tần số thấp và một lượng nhỏ thông tin tần số cao. Nếu chúng ta biến các thành phần dưới cùng bên phải của mỗi ma trận DCT thành 0, thì hình ảnh thu được sẽ vẫn giống như vậy bởi vì, như tôi đã đề cập, con người rất tệ trong việc quan sát các thay đổi tần số cao. Đây chính xác là những gì chúng tôi làm trong bước tiếp theo

Tôi tìm thấy một video tuyệt vời về chủ đề này. Xem nó nếu DCT không có quá nhiều ý nghĩa

Tất cả chúng ta đều đã nghe nói rằng JPEG là một thuật toán nén bị mất dữ liệu nhưng cho đến nay chúng tôi vẫn chưa thực hiện bất kỳ điều gì làm mất dữ liệu. Chúng tôi chỉ chuyển đổi các khối 8x8 của các thành phần YUV thành các khối hàm cosine 8x8 mà không làm mất thông tin. Phần mất dữ liệu xuất hiện trong bước lượng tử hóa

Lượng tử hóa là một quá trình trong đó chúng tôi lấy một vài giá trị trong một phạm vi cụ thể và biến chúng thành một giá trị riêng biệt. Đối với trường hợp của chúng tôi, đây chỉ là một cái tên ưa thích để chuyển đổi các hệ số tần số cao hơn trong ma trận đầu ra DCT thành 0. Khi bạn lưu ảnh bằng JPEG, hầu hết các chương trình chỉnh sửa ảnh đều hỏi bạn cần nén bao nhiêu. Tỷ lệ phần trăm bạn cung cấp ở đó ảnh hưởng đến mức độ lượng tử hóa được áp dụng và lượng thông tin tần số cao hơn bị mất. Đây là nơi áp dụng nén mất dữ liệu. Khi bạn mất thông tin tần số cao, bạn không thể tạo lại hình ảnh gốc chính xác từ hình ảnh JPEG thu được

Tùy thuộc vào mức độ nén được yêu cầu, một số ma trận lượng tử hóa phổ biến được sử dụng [thực tế thú vị. Hầu hết các nhà cung cấp đều có bằng sáng chế về xây dựng bảng lượng tử hóa]. Chúng tôi chia phần tử ma trận hệ số DCT với ma trận lượng tử hóa, làm tròn kết quả thành một số nguyên và nhận ma trận lượng tử hóa. Hãy đi qua một ví dụ

Nếu bạn có ma trận DCT này

Ma trận lượng tử hóa [phổ biến] này

Sau đó, ma trận lượng tử hóa kết quả sẽ là thế này

Mặc dù con người không thể nhìn thấy thông tin tần số cao, nhưng nếu bạn loại bỏ quá nhiều thông tin khỏi khối hình ảnh 8x8, hình ảnh tổng thể sẽ trông có vẻ khối. Trong ma trận lượng tử hóa này, giá trị đầu tiên được gọi là giá trị DC và các giá trị còn lại là giá trị AC. Nếu chúng tôi lấy các giá trị DC từ tất cả các ma trận được lượng tử hóa và tạo một hình ảnh mới, về cơ bản chúng tôi sẽ có một hình thu nhỏ với độ phân giải 1/8 của hình ảnh gốc

Cũng cần lưu ý rằng vì chúng tôi áp dụng lượng tử hóa trong khi giải mã, chúng tôi sẽ phải đảm bảo màu sắc nằm trong phạm vi [0,255]. Nếu chúng nằm ngoài phạm vi này, chúng tôi sẽ phải tự kẹp chúng vào phạm vi này

ngoằn ngoèo

Sau khi lượng tử hóa, JPEG sử dụng mã hóa zig-zag để chuyển đổi ma trận thành 1D [img src]

Hãy tưởng tượng chúng ta có ma trận lượng tử hóa này

Đầu ra của mã hóa zig-zag sẽ là thế này

import glob
4

Mã hóa này được ưu tiên vì hầu hết thông tin tần số thấp [quan trọng nhất] được lưu trữ ở đầu ma trận sau khi lượng tử hóa và mã hóa zig-zag lưu trữ tất cả thông tin đó ở đầu ma trận 1D. Điều này hữu ích cho quá trình nén xảy ra trong bước tiếp theo

Độ dài chạy và mã hóa Delta

Mã hóa độ dài chạy được sử dụng để nén dữ liệu lặp lại. Khi kết thúc quá trình mã hóa zig-zag, chúng ta đã thấy hầu hết các mảng 1D được mã hóa zig-zag đều có rất nhiều số 0 ở cuối. Mã hóa độ dài chạy cho phép chúng tôi lấy lại tất cả dung lượng bị lãng phí đó và sử dụng ít byte hơn để biểu thị tất cả các số 0 đó. Hãy tưởng tượng bạn có một số dữ liệu như thế này

import glob
9

Mã hóa độ dài chạy sẽ chuyển đổi nó thành

import glob
0

Chúng tôi đã có thể nén thành công 7 byte dữ liệu thành chỉ 2 byte

Mã hóa delta là một kỹ thuật được sử dụng để biểu diễn một byte so với byte trước nó. Nó dễ hiểu hơn với một ví dụ. Giả sử bạn có dữ liệu sau

import glob
1

Bạn có thể sử dụng mã hóa delta để lưu trữ nó như thế này

import glob
8

Trong JPEG, mọi giá trị DC trong ma trận hệ số DCT được mã hóa delta tương ứng với giá trị DC trước nó. Điều này có nghĩa là nếu bạn thay đổi hệ số DCT đầu tiên của hình ảnh, toàn bộ hình ảnh sẽ bị hỏng nhưng nếu bạn sửa đổi giá trị đầu tiên của ma trận DCT cuối cùng, thì chỉ một phần rất nhỏ của hình ảnh sẽ bị ảnh hưởng. Điều này rất hữu ích vì giá trị DC đầu tiên trong hình ảnh của bạn thường đa dạng nhất và bằng cách áp dụng mã hóa Delta, chúng tôi đưa các giá trị DC còn lại về gần 0 và điều đó dẫn đến khả năng nén tốt hơn trong bước tiếp theo của Mã hóa Huffman

Mã hóa Huffman

Mã hóa Huffman là một phương pháp nén thông tin không mất dữ liệu. Huffman đã từng tự hỏi: “Số lượng bit nhỏ nhất mà tôi có thể sử dụng để lưu trữ một đoạn văn bản tùy ý là bao nhiêu?”. Định dạng mã hóa này là câu trả lời của anh ấy. Hãy tưởng tượng bạn phải lưu trữ văn bản này

import glob
9

Trong một kịch bản bình thường, mỗi ký tự sẽ chiếm 1 byte dung lượng

import glob
0

Điều này dựa trên ASCII để ánh xạ nhị phân. Nhưng nếu chúng ta có thể đưa ra một ánh xạ tùy chỉnh thì sao?

import glob
1

Bây giờ chúng ta có thể lưu trữ cùng một văn bản bằng cách sử dụng ít bit hơn

import glob
2

Điều này là tốt và tốt nhưng nếu chúng ta muốn chiếm ít không gian hơn thì sao?

import glob
40

Mã hóa Huffman cho phép chúng tôi sử dụng loại ánh xạ có độ dài thay đổi này. Nó lấy một số dữ liệu đầu vào, ánh xạ các ký tự thường xuyên nhất thành các mẫu bit nhỏ hơn và các ký tự ít thường xuyên nhất thành các mẫu bit lớn hơn và cuối cùng tổ chức ánh xạ thành cây nhị phân. Trong JPEG, chúng tôi lưu trữ thông tin DCT [Biến đổi Cosine rời rạc] bằng cách sử dụng mã hóa Huffman. Hãy nhớ rằng tôi đã nói với bạn rằng việc sử dụng mã hóa delta cho các giá trị DC sẽ giúp ích cho Mã hóa Huffman? . Sau khi mã hóa delta, chúng tôi kết thúc với ít “ký tự” hơn để lập bản đồ và tổng kích thước của cây Huffman của chúng tôi bị giảm

Tom Scott có một video tuyệt vời với hình ảnh động về cách hoạt động của mã hóa Huffman nói chung. Hãy xem nó trước khi tiếp tục

Một JPEG chứa tối đa 4 bảng Huffman và những bảng này được lưu trữ trong phần “Xác định bảng Huffman” [bắt đầu bằng

import glob
13]. Các hệ số DCT được lưu trữ trong 2 bảng Huffman khác nhau. Một cái chỉ chứa các giá trị DC từ các bảng zig-zag và cái kia chứa các giá trị AC từ các bảng zig-zag. Điều này có nghĩa là trong quá trình giải mã, chúng ta sẽ phải hợp nhất các giá trị DC và AC từ hai ma trận riêng biệt. Thông tin DCT cho kênh độ chói và màu sắc được lưu trữ riêng biệt, vì vậy chúng tôi có 2 bộ thông tin DC và 2 bộ thông tin AC, cung cấp cho chúng tôi tổng cộng 4 bảng Huffman

Trong một hình ảnh thang độ xám, chúng tôi sẽ chỉ có 2 bảng Huffman [1 cho DC và 1 cho AC] vì chúng tôi không quan tâm đến màu sắc. Như bạn có thể hình dung, 2 hình ảnh có thể có các bảng Huffman rất khác nhau, vì vậy điều quan trọng là phải lưu trữ các bảng này bên trong mỗi JPEG

Vì vậy, chúng tôi biết các chi tiết cơ bản về những gì hình ảnh JPEG chứa. Hãy bắt đầu với việc giải mã

giải mã JPEG

Chúng ta có thể chia quá trình giải mã thành nhiều bước

  1. Trích xuất các bảng Huffman và giải mã các bit
  2. Trích xuất các hệ số DCT bằng cách hoàn tác mã hóa độ dài chạy và mã hóa delta
  3. Sử dụng các hệ số DCT để kết hợp các sóng cosin và tạo lại các giá trị pixel cho mỗi khối 8x8
  4. Chuyển đổi YCbCr sang RGB cho từng pixel
  5. Hiển thị hình ảnh RGB kết quả

Chuẩn JPEG hỗ trợ 4 định dạng nén

  • đường cơ sở
  • tuần tự mở rộng
  • Cấp tiến
  • Không mất mát

Chúng tôi sẽ làm việc với nén Baseline và theo tiêu chuẩn, đường cơ sở sẽ chứa một loạt các khối 8x8 ngay cạnh nhau. Các định dạng nén khác bố trí dữ liệu hơi khác một chút. Chỉ để tham khảo, tôi đã tô màu các phân đoạn khác nhau trong nội dung hex của hình ảnh chúng tôi đang sử dụng. Cái này nó thì trông như thế nào

Trích xuất các bảng Huffman

Chúng ta đã biết rằng một JPEG chứa 4 bảng Huffman. Đây là bước cuối cùng trong quy trình mã hóa, vì vậy đây phải là bước đầu tiên trong quy trình giải mã. Mỗi phần DHT chứa các thông tin sau

FieldSizeDescriptionMarker Identifier2 bytes0xff, 0xc4 để xác định DHT markerLength2 bytesĐiều này chỉ định độ dài của bảng HuffmanThông tin HT1 bytebit 0. 3. số HT [0. 3, nếu không thì lỗi]
bit 4. loại HT, 0 = bảng DC, 1 = bảng AC
bit 5. 7. không được sử dụng, phải là 0Số ký hiệu16 byteSố ký hiệu có mã độ dài 1. 16, tổng[n] của các byte này là tổng số mã, phải là

Giả sử bạn có một bảng DH tương tự như thế này [src]

Ký hiệu Mã HuffmanĐộ dài mã a002b0103c0113d1003e1013f1103g11104h111105i1111106j11111107k111111108l1111111109

Nó sẽ được lưu trữ trong tệp JFIF đại khái như thế này [chúng sẽ được lưu trữ ở dạng nhị phân. Tôi đang sử dụng ASCII chỉ cho mục đích minh họa]

import glob
41

0 có nghĩa là không có mã Huffman có độ dài 1. 1 nghĩa là có 1 mã Huffman độ dài 2. Và như thế. Luôn có dữ liệu độ dài 16 byte trong phần DHT ngay sau thông tin lớp và ID. Hãy viết một số mã để trích xuất độ dài và các phần tử trong DHT

import glob
42

Nếu bạn chạy mã, nó sẽ tạo ra đầu ra sau

import glob
43

Ngọt. Chúng tôi có độ dài và các yếu tố. Bây giờ chúng ta cần tạo một lớp bảng Huffman tùy chỉnh để chúng ta có thể tạo lại cây nhị phân từ các phần tử và độ dài này. Tôi xấu hổ sao chép mã này từ đây

import glob
44

import glob
14 lấy độ dài và phần tử, lặp qua tất cả các phần tử và đặt chúng vào danh sách
import glob
15. Danh sách này chứa các danh sách lồng nhau và đại diện cho một cây nhị phân. Bạn có thể đọc trực tuyến cách thức hoạt động của Cây Huffman và cách tạo cấu trúc dữ liệu cây Huffman của riêng bạn bằng Python. Đối với DHT đầu tiên của chúng tôi [sử dụng hình ảnh tôi đã liên kết khi bắt đầu hướng dẫn này], chúng tôi có các dữ liệu, độ dài và thành phần sau

import glob
45

Sau khi gọi

import glob
14 về điều này, danh sách
import glob
15 sẽ chứa dữ liệu này

import glob
46

import glob
18 cũng chứa phương thức
import glob
19 duyệt cây cho chúng ta và trả lại cho chúng ta các bit đã giải mã bằng bảng Huffman. Phương pháp này mong đợi một dòng bit làm đầu vào. Dòng bit chỉ là biểu diễn nhị phân của dữ liệu. Ví dụ: dòng bit điển hình của
import glob
80 sẽ là
import glob
81. Trước tiên, chúng tôi chuyển đổi từng ký tự thành mã ASCII của nó và sau đó chuyển đổi mã ASCII đó thành nhị phân. Hãy tạo một lớp tùy chỉnh cho phép chúng ta chuyển đổi một chuỗi thành bit và đọc từng bit một. Đây là cách chúng tôi sẽ thực hiện nó

import glob
47

Chúng tôi cung cấp cho lớp này một số dữ liệu nhị phân trong khi khởi tạo nó và sau đó sử dụng các phương thức

import glob
82 và
import glob
83 để đọc nó

Giải mã bảng lượng tử hóa

Phần Xác định bảng lượng tử chứa dữ liệu sau

FieldSizeDescriptionMarker Identifier2 byte0xff, 0xdb xác định DQTLength2 byteĐiều này cho biết độ dài của QT. Thông tin QT1 bytebit 0. 3. số lượng QT [0. 3, nếu không thì lỗi] bit 4. 7. độ chính xác của QT, 0 = 8 bit, nếu không thì 16 bitBytesn byteĐiều này cho giá trị QT, n = 64*[độ chính xác+1]

Theo chuẩn JPEG, có 2 bảng lượng tử hóa mặc định trong một ảnh JPEG. Một cho độ chói và một cho sắc độ. Các bảng này bắt đầu từ điểm đánh dấu

import glob
84. Trong mã ban đầu chúng tôi đã viết, chúng tôi đã thấy rằng đầu ra chứa hai điểm đánh dấu
import glob
84. Hãy mở rộng mã mà chúng tôi đã có và thêm khả năng giải mã các bảng lượng tử hóa

import glob
48

Chúng tôi đã làm một vài điều ở đây. Đầu tiên, tôi định nghĩa một phương thức

import glob
86. Nó chỉ là một phương pháp tiện dụng để giải mã một số lượng byte khác nhau từ dữ liệu nhị phân. Tôi đã thay thế một số mã trong phương thức
import glob
87 để sử dụng chức năng mới này. Sau đó, tôi đã định nghĩa phương thức
import glob
88. Phương pháp này chỉ cần đọc tiêu đề của phần Bảng lượng tử hóa và sau đó nối thêm dữ liệu lượng tử hóa vào từ điển với giá trị tiêu đề là khóa. Giá trị tiêu đề sẽ là 0 cho độ chói và 1 cho sắc độ. Mỗi phần Bảng lượng tử hóa trong JFIF chứa 64 byte dữ liệu QT [đối với ma trận lượng tử hóa 8x8 của chúng tôi]

Nếu chúng ta in ma trận lượng tử hóa cho hình ảnh của mình. Họ sẽ trông như thế này

import glob
49

Giải mã bắt đầu khung hình

Phần Bắt đầu của Khung chứa các thông tin sau [src]

FieldSizeDescriptionĐịnh danh Marker2 byte0xff, 0xc0 để xác định điểm đánh dấu SOF0Độ dài2 byteGiá trị này bằng 8 + thành phần*3 giá trịĐộ chính xác của dữ liệu1 byteĐây là bit/mẫu, thường là 8 [12 và 16 không được hầu hết các phần mềm hỗ trợ]. Chiều cao hình ảnh2 byteĐây phải là > 0Chiều rộng hình ảnh2 byteĐây phải là > 0Số lượng thành phần1 byteThông thường 1 = thang màu xám, 3 = màu YcbCr hoặc YIQMỗi thành phần3 byteĐọc từng dữ liệu thành phần gồm 3 byte. Nó chứa, [Id thành phần[1byte][1 = Y, 2 = Cb, 3 = Cr, 4 = I, 5 = Q], hệ số lấy mẫu [1byte] [bit 0-3 dọc. , 4-7 ngang. ], số bảng lượng tử hóa [1 byte]]

Trong số dữ liệu này, chúng tôi chỉ quan tâm đến một số điều. Chúng tôi sẽ trích xuất chiều rộng và chiều cao của hình ảnh và số bảng lượng tử hóa của từng thành phần. Chiều rộng và chiều cao sẽ được sử dụng khi chúng tôi bắt đầu giải mã các bản quét hình ảnh thực tế từ phần Bắt đầu quét. Vì chúng ta sẽ chủ yếu làm việc với ảnh YCbCr, nên chúng ta có thể mong đợi số lượng thành phần bằng 3 và loại thành phần lần lượt bằng 1, 2 và 3. Hãy viết một số mã để giải mã dữ liệu này

import glob
90

Chúng tôi đã thêm thuộc tính danh sách

import glob
89 vào lớp JPEG của mình và giới thiệu phương thức
import glob
90. Phương pháp
import glob
90 giải mã dữ liệu cần thiết từ phần SOF và đặt số bảng lượng tử hóa của từng thành phần trong danh sách
import glob
89. Chúng tôi sẽ sử dụng ánh xạ này khi chúng tôi bắt đầu đọc phần Bắt đầu quét. Đây là giao diện của
import glob
89 đối với hình ảnh của chúng tôi

import glob
91

Giải mã Bắt đầu quét

Ngọt. Chúng ta chỉ còn một phần nữa để giải mã. Đây là phần chính của hình ảnh JPEG và chứa dữ liệu "hình ảnh" thực tế. Đây cũng là bước phức tạp nhất. Mọi thứ khác mà chúng tôi đã giải mã cho đến nay có thể được coi là tạo bản đồ để giúp chúng tôi điều hướng và giải mã hình ảnh thực tế. Phần này chứa chính hình ảnh thực tế [mặc dù ở dạng được mã hóa]. Chúng tôi sẽ đọc phần này và sử dụng dữ liệu chúng tôi đã giải mã để hiểu ý nghĩa của hình ảnh

Tất cả các điểm đánh dấu chúng tôi đã thấy cho đến nay đều bắt đầu bằng

import glob
94.
import glob
94 cũng có thể là một phần của dữ liệu quét hình ảnh nhưng nếu có
import glob
94 trong dữ liệu quét thì nó sẽ luôn được xử lý bởi
import glob
97. Đây là điều mà bộ mã hóa JPEG thực hiện tự động và được gọi là nhồi byte. Nhiệm vụ của người giải mã là loại bỏ thủ tục tố tụng này
import glob
97. Hãy bắt đầu phương pháp giải mã SOS với chức năng này và loại bỏ
import glob
97 nếu nó hiện diện. Trong hình ảnh mẫu tôi đang sử dụng, chúng tôi không có
import glob
94 trong dữ liệu quét hình ảnh nhưng nó vẫn là một bổ sung hữu ích

import glob
92

Trước đây, tôi đã tìm kiếm thủ công đến cuối tệp bất cứ khi nào tôi gặp điểm đánh dấu

import glob
01 nhưng bây giờ chúng tôi đã có sẵn công cụ cần thiết để duyệt qua toàn bộ tệp theo thứ tự có hệ thống, tôi đã chuyển điều kiện đánh dấu vào bên trong mệnh đề
import glob
02. Hàm
import glob
03 chỉ bị ngắt bất cứ khi nào nó quan sát thấy thứ gì đó không phải là
import glob
97 sau
import glob
94. Do đó, nó sẽ thoát ra khỏi vòng lặp khi gặp phải
import glob
06 và bằng cách đó, chúng tôi có thể tìm kiếm đến cuối tệp một cách an toàn mà không gặp bất kỳ sự cố nào. Nếu bạn chạy mã này ngay bây giờ, sẽ không có gì mới xuất ra thiết bị đầu cuối

Nhớ lại rằng JPEG đã chia hình ảnh thành ma trận 8x8. Bước tiếp theo đối với chúng tôi là chuyển đổi dữ liệu quét hình ảnh của chúng tôi thành luồng bit và xử lý luồng theo khối dữ liệu 8x8. Hãy thêm một số mã vào lớp của chúng tôi

import glob
93

Chúng tôi bắt đầu bằng cách chuyển đổi dữ liệu quét của mình thành luồng bit. Sau đó, chúng tôi khởi tạo

import glob
07,
import glob
08,
import glob
09 thành 0. Những điều này là bắt buộc vì hãy nhớ rằng chúng ta đã nói về cách phần tử DC trong ma trận lượng tử hóa [phần tử đầu tiên của ma trận] được mã hóa delta so với phần tử DC trước đó?

Vòng lặp

import glob
10 có vẻ hơi thú vị.
import glob
11 cho biết chúng ta có thể chia chiều cao cho 8 bao nhiêu lần. Điều tương tự cũng xảy ra với
import glob
12. Tóm lại, điều này cho chúng ta biết hình ảnh được chia thành bao nhiêu ma trận 8x8

import glob
13 sẽ lấy bảng lượng tử hóa và một số tham số bổ sung, tạo Ma trận biến đổi Cosine rời rạc nghịch đảo và cung cấp cho chúng ta các ma trận Y, Cr và Cb. Việc chuyển đổi thực tế các ma trận này thành RGB sẽ diễn ra trong hàm
import glob
14

Trước tiên, hãy tạo lớp IDCT của chúng ta và sau đó chúng ta có thể bắt đầu bổ sung phương thức

import glob
13

import glob
94

Hãy cố gắng hiểu lớp IDCT này từng bước. Khi chúng tôi trích xuất MCU từ JPEG, thuộc tính

import glob
16 của lớp này sẽ lưu trữ nó. Sau đó, chúng tôi sẽ sắp xếp lại ma trận MCU bằng cách hoàn tác mã hóa ngoằn ngoèo thông qua phương pháp
import glob
17. Cuối cùng, chúng tôi sẽ hoàn tác Biến đổi Cosine rời rạc bằng cách gọi phương thức
import glob
18

Nếu bạn còn nhớ, bảng Discrete Cosine là cố định. Cách tính toán thực tế cho một DCT hoạt động nằm ngoài phạm vi của hướng dẫn này vì nó thiên về toán hơn là lập trình. Chúng ta có thể lưu trữ bảng này dưới dạng một biến toàn cục và sau đó truy vấn giá trị đó dựa trên các cặp x, y. Tôi quyết định đặt bảng và tính toán của nó trong lớp

import glob
19 cho mục đích dễ đọc. Mỗi phần tử của ma trận MCU được sắp xếp lại được nhân với các giá trị của
import glob
20 và cuối cùng chúng tôi nhận lại các giá trị Y, Cr và Cb

Điều này sẽ có ý nghĩa hơn khi chúng ta viết ra phương thức

import glob
13

Nếu bạn sửa đổi bảng ngoằn ngoèo thành một cái gì đó như thế này

import glob
95

Bạn sẽ có đầu ra sau [chú ý các đồ tạo tác nhỏ]

Và nếu bạn dũng cảm, bạn có thể sửa đổi bảng ngoằn ngoèo hơn nữa

import glob
96

Nó sẽ dẫn đến đầu ra này

Bây giờ, hãy kết thúc phương pháp

import glob
13 của chúng ta

import glob
97

Chúng tôi bắt đầu bằng cách tạo một lớp Biến đổi Cosine rời rạc nghịch đảo [

import glob
23]. Sau đó, chúng tôi đọc trong luồng bit và giải mã nó bằng bảng Huffman của chúng tôi

import glob
24 và
import glob
25 lần lượt đề cập đến các bảng DC về độ chói và sắc độ và
import glob
26 và
import glob
27 đề cập đến các bảng AC về độ chói và sắc độ tương ứng

Sau khi chúng tôi giải mã luồng bit, chúng tôi trích xuất hệ số DC được mã hóa delta mới bằng cách sử dụng hàm

import glob
28 và thêm
import glob
29 vào nó để có được hệ số DC được giải mã delta

Sau đó, chúng tôi lặp lại quy trình giải mã tương tự nhưng đối với các giá trị AC trong ma trận lượng tử hóa. Giá trị mã của

import glob
400 gợi ý rằng chúng tôi đã gặp phải điểm đánh dấu Kết thúc Khối [EOB] và chúng tôi cần dừng. Hơn nữa, phần đầu tiên của bảng lượng tử AC cho chúng ta biết chúng ta có bao nhiêu số 0 đứng đầu. Hãy nhớ mã hóa độ dài chạy mà chúng ta đã nói trong phần đầu tiên? . Chúng tôi giải mã mã hóa độ dài chạy và bỏ qua nhiều bit đó. Tất cả các bit bị bỏ qua đều được đặt thành 0 hoàn toàn trong lớp
import glob
19

Khi chúng tôi đã giải mã các giá trị DC và AC cho một MCU, chúng tôi sắp xếp lại MCU và hoàn tác mã hóa ngoằn ngoèo bằng cách gọi

import glob
17 và sau đó chúng tôi thực hiện đảo ngược DCT trên MCU đã giải mã

Phương thức

import glob
13 sẽ trả về ma trận DCT nghịch đảo và giá trị của hệ số DC. Hãy nhớ rằng, ma trận DCT nghịch đảo này chỉ dành cho một ma trận MCU [Đơn vị được mã hóa tối thiểu] 8x8 nhỏ. Chúng tôi sẽ làm điều này cho tất cả các MCU riêng lẻ trong toàn bộ tệp hình ảnh

Hiển thị hình ảnh trên màn hình

Hãy sửa đổi mã của chúng ta một chút và tạo Tkinter Canvas và vẽ từng MCU sau khi giải mã nó theo phương thức

import glob
404

import glob
98

Hãy bắt đầu với các hàm

import glob
405 và
import glob
406.
import glob
405 nhận các giá trị Y, Cr và Cb, sử dụng công thức để chuyển đổi các giá trị này thành các giá trị RGB của chúng, sau đó xuất các giá trị RGB được kẹp. Bạn có thể thắc mắc tại sao chúng tôi lại thêm 128 vào các giá trị RGB. Nếu bạn còn nhớ, trước khi máy nén JPEG áp dụng DCT trên MCU, nó sẽ trừ 128 khỏi các giá trị màu. Nếu màu ban đầu nằm trong phạm vi [0,255], JPEG sẽ đặt chúng vào phạm vi [-128,+128]. Vì vậy, chúng tôi phải hoàn tác hiệu ứng đó khi giải mã JPEG và đó là lý do tại sao chúng tôi thêm 128 vào RGB. Đối với
import glob
406, trong quá trình giải nén, giá trị đầu ra có thể vượt quá [0,255] nên chúng tôi kẹp chúng trong khoảng [0,255]

Trong phương pháp

import glob
14, chúng tôi lặp qua từng ma trận Y, Cr và Cb được giải mã 8x8 và chuyển đổi từng phần tử của ma trận 8x8 thành các giá trị RGB. Sau khi chuyển đổi, chúng tôi vẽ từng pixel trên Tkinter
import glob
410 bằng phương pháp
import glob
411. Bạn có thể tìm thấy mã hoàn chỉnh trên GitHub. Bây giờ nếu bạn chạy mã này, khuôn mặt của tôi sẽ hiển thị trên màn hình của bạn 😄

Phần kết luận

Oh Boy. Ai có thể nghĩ rằng nó sẽ mất 6000 từ + lời giải thích để hiển thị khuôn mặt của tôi trên màn hình. Tôi ngạc nhiên bởi sự thông minh của một số nhà phát minh thuật toán này. Tôi hy vọng bạn thích bài viết này nhiều như tôi thích viết nó. Tôi đã học được rất nhiều điều khi viết bộ giải mã này. Tôi chưa bao giờ nhận ra có bao nhiêu phép toán phức tạp được sử dụng để mã hóa một hình ảnh JPEG đơn giản. Tôi có thể làm việc với hình ảnh PNG tiếp theo và thử viết bộ giải mã cho hình ảnh PNG. Bạn cũng nên thử viết bộ giải mã cho PNG [hoặc một số định dạng khác]. Tôi chắc chắn rằng nó sẽ liên quan đến rất nhiều việc học và thậm chí còn thú vị hơn nữa 😅

Dù bằng cách nào, bây giờ tôi mệt mỏi. Tôi đã nhìn chằm chằm vào hex quá lâu và tôi nghĩ rằng mình đã kiếm được một kỳ nghỉ xứng đáng. Tất cả các bạn hãy cẩn thận và nếu bạn có bất kỳ câu hỏi nào, vui lòng viết chúng trong phần bình luận bên dưới. Tôi còn rất mới với cuộc phiêu lưu viết mã JPEG này nhưng tôi sẽ cố gắng trả lời nhiều nhất có thể 😄

Từ biệt. 👋 ❤️

đọc thêm

Nếu bạn muốn đi sâu vào chi tiết hơn, bạn có thể xem một số tài nguyên tôi đã sử dụng khi viết bài này. Tôi cũng đã thêm một số liên kết bổ sung cho một số nội dung thú vị liên quan đến JPEG

Tôi có thể đổi tên JFIF thành JPG không?

Giải pháp cho vấn đề này quá dễ dàng, nó thực sự có vẻ quá tốt để trở thành sự thật. Bạn chỉ có thể đổi tên tệp . Không cần chuyển đổi hình ảnh. Đừng mở jfif trong MS Paint hoặc Photoshop và lưu lại nó. Chỉ cần nhấp vào tên tệp, nhấn backspace bốn lần, nhập jpg [hoặc jpeg] và nhấn Enter.

JFIF và JPG có giống nhau không?

JFIF là một biến thể của định dạng tệp JPEG/JPG . Định dạng JFIF xác định các thông số kỹ thuật bổ sung và chủ yếu được sử dụng bởi máy ảnh. Trong khi đó, các cải tiến đã được thực hiện đối với định dạng JPEG và định dạng JFIF hầu như đã lỗi thời.

Tại sao JFIF không phải là JPG?

Đôi khi Windows 10 lưu tệp JPG dưới dạng tệp JFIF. Nó dường như xảy ra thường xuyên nhất khi lưu hình ảnh trong trình duyệt nhưng dường như không giới hạn ở chúng. Nguyên nhân của sự cố là do liên kết tệp không chính xác trong sổ đăng ký xuất hiện sau bản cập nhật Windows 10 Creators .

Làm cách nào để chuyển đổi RGB sang JPG bằng Python?

Tôi có ý nghĩ về. .
Tạo một hình ảnh jpg mới bằng cách sử dụng. Rebuilded_image = Hình ảnh. mới['RGB', [x, y]]
Thay đổi giá trị pixel RGB của Rebuilded_image bằng giá trị của image_RGB

Chủ Đề