5.32. Lưu ảnh và nén¶
Mọi trang trước đây đều làm việc với ảnh trực tiếp trên camera: chụp vào bộ đệm khung hình hoặc cấp phát trên heap MicroPython, xử lý qua các phương thức của module image, rồi hiển thị trong giao diện xem trước của IDE hoặc đưa vào giai đoạn tiếp theo trong cùng tập lệnh. Hầu hết các ứng dụng đều cần làm điều ngược lại ở một thời điểm nào đó: lấy một ảnh đang ở trong RAM và lưu nó vào nơi lâu dài -- thẻ SD, máy chủ USB, qua mạng -- nơi thiết bị khác ngoài camera có thể đọc được.
Module image cung cấp hai hướng cho công việc đó. Hướng save ghi ảnh vào một tệp trên hệ thống tệp, định dạng tệp được chọn theo phần mở rộng và các chi tiết mã hóa được phương thức xử lý. Hướng to-format trả về một đối tượng Image chứa luồng byte đã được mã hóa, phù hợp để truyền cho lệnh gọi streaming hoặc kết nối mạng mà không cần động đến hệ thống tệp. Mỗi hướng phù hợp với các ứng dụng khác nhau; cả hai đều dựa trên cùng một bộ nén phía dưới.
5.32.1. Lưu vào tệp¶
save() ghi ảnh vào hệ thống tệp theo đường dẫn chỉ định:
img.save("/sdcard/capture.jpg")
img.save("/sdcard/capture.bmp")
img.save("/sdcard/region.jpg", roi=(40, 60, 200, 150), quality=85)
Định dạng được xác định từ phần mở rộng của tệp. Có năm phần mở rộng được nhận dạng: .bmp ghi định dạng Windows bitmap (không mất dữ liệu, không nén, ghi lại từng điểm ảnh đã chụp); .pgm ghi định dạng portable graymap (không mất dữ liệu, chỉ hỗ trợ thang xám); .ppm ghi định dạng portable pixmap (không mất dữ liệu, RGB); .jpg và .jpeg đều ghi JPEG (có mất dữ liệu, nén). Ảnh đầu vào phải ở đúng định dạng màu sắc phù hợp với định dạng tệp đã chọn -- lưu ảnh màu dưới dạng .pgm sẽ gây ra lỗi.
roi giới hạn việc lưu vào một vùng hình chữ nhật con của ảnh, giống như cách tham số roi của mọi phương thức khác trong module image hoạt động. Mặc định là toàn bộ ảnh. Từ khóa này bị bỏ qua khi lưu ảnh nén JPEG vì dữ liệu trên đĩa đã bao gồm toàn bộ khung hình và việc mã hóa lại qua cắt xén sẽ làm mất ý nghĩa của việc lưu các byte nén sẵn.
quality là chất lượng nén JPEG từ 0 đến 100 và chỉ có ý nghĩa khi đầu ra là JPEG (từ khóa bị bỏ qua với các định dạng không mất dữ liệu). Giá trị mặc định 50 là mức cân bằng phù hợp cho hầu hết các ứng dụng; 70 đến 85 là dải cho chất lượng hình ảnh cao hơn, 30 đến 50 là phạm vi phù hợp cho ảnh thu nhỏ và truyền dẫn bị giới hạn băng thông, còn 90 trở lên dành cho các trường hợp ảnh sẽ được kiểm tra thủ công hoặc đưa qua thuật toán phía sau nhạy cảm với nhiễu nén.
Ảnh đầu vào được trả về nên lệnh gọi có thể được nối chuỗi: img.save("/sdcard/x.jpg").draw_string(0, 0, "saved"). Đối tượng trả về là cùng một ảnh trong bộ nhớ; việc lưu là tác dụng phụ.
Một cách dùng phổ biến là mẫu chụp-và-ghi-log. Một kích hoạt xảy ra (phát hiện vùng màu (blob), nhấn nút, bộ định thời hết giờ); tập lệnh chụp một khung hình; nó thêm nhãn thời gian vào tên tệp; và gọi save() để đẩy ảnh lên thẻ SD. Giao diện xem trước của IDE tiếp tục chạy, kích hoạt tiếp theo xảy ra và các tệp đã lưu tích lũy dần.
5.32.2. Mã hóa vào bộ nhớ¶
Khi đích đến không phải hệ thống tệp mà là kết nối mạng, cổng nối tiếp, hoặc đầu vào của một module khác, ứng dụng cần luồng byte đã mã hóa trong bộ nhớ thay vì trên đĩa. to_jpeg() và to_png() tạo ra chính xác điều đó:
encoded = img.to_jpeg(quality=80, copy=True)
bytes_to_send = encoded.bytearray()
sock.send(bytes_to_send)
Hành vi mặc định là chuyển đổi tại chỗ: ảnh đầu vào được chuyển đổi thành ảnh JPEG (hoặc PNG) và cùng đối tượng đó được trả về. Với copy=True, quá trình chuyển đổi ghi vào một đối tượng heap mới được cấp phát; với copy_to_fb=True, đầu ra được đặt vào bộ đệm khung hình. Sự lựa chọn giống như bất kỳ phương thức chuyển đổi nào khác -- tại chỗ theo mặc định, sao chép khi cần giữ lại ảnh gốc sau này.
quality và subsampling là các tham số điều chỉnh JPEG giống như những gì hướng save cung cấp. subsampling chọn phương thức lấy mẫu con chroma: image.JPEG_SUBSAMPLING_AUTO chọn tốt nhất cho chất lượng đã chọn, image.JPEG_SUBSAMPLING_444 giữ chroma ở độ phân giải đầy đủ (tệp lớn nhất, độ chính xác màu tốt nhất), image.JPEG_SUBSAMPLING_422 và image.JPEG_SUBSAMPLING_420 giảm một nửa độ phân giải chroma theo một hoặc cả hai trục (tệp nhỏ hơn, màu sắc hơi mềm hơn nhưng không thể thấy ở khoảng cách xem thông thường). Mặc định AUTO là lựa chọn phù hợp trừ khi ứng dụng có nhu cầu cụ thể.
PNG qua to_png() là lossless nhưng mã hóa chậm hơn và tạo ra các tệp lớn hơn JPEG cho nội dung ảnh chụp (nội dung ảnh chụp nén kém dưới phương thức dự đoán của PNG). Dùng PNG khi ảnh là đường nét, ảnh chụp màn hình, hoặc chứa đồ họa cạnh sắc được vẽ lên trên một khung hình đã chụp -- mã hóa lossless bảo toàn các cạnh sắc mà JPEG sẽ làm mờ. Ngược lại JPEG là lựa chọn mặc định phù hợp.
Cả to_jpeg() và to_png() đều chấp nhận các từ khóa vị trí và tỷ lệ theo kiểu vẽ giống như các phương thức chuyển đổi khác -- x_scale, y_scale, roi, rgb_channel, alpha, color_palette, alpha_palette, hint -- vì vậy cùng một lệnh gọi có thể mã hóa phiên bản đã thu phóng, cắt xén hoặc ánh xạ bảng màu của nguồn trong một bước. compress() là tên cũ của to_jpeg(); cả hai nhận cùng tham số và tạo ra cùng kết quả.
5.32.3. Lợi ích của nén¶
Các con số đằng sau sự đánh đổi giữa JPEG và dữ liệu thô đáng được xem xét một lần.
Một khung hình RGB565 320x240 có 153.600 byte (một khung hình chụp ở QVGA). Một khung hình 640x480 là 614.400 byte; một khung hình 1280x960 là 2.457.600 byte. Những con số đó không lớn so với màn hình máy tính bàn hay điện thoại, nhưng chúng đáng kể trong bối cảnh một camera chỉ có vài MB RAM, một thẻ SD với băng thông ghi hữu hạn, và một liên kết đến máy chủ thường chạy qua USB CDC, UART, hoặc module không dây ở tốc độ khiêm tốn.
JPEG ở quality=50 thường nén một khung hình ảnh chụp ảnh từ 10 đến 20 lần: khung hình VGA 614 KB trở thành luồng byte mã hóa từ 30 đến 60 KB. Ở quality=85, tỷ lệ nén giảm xuống 5 đến 10 lần (60 đến 120 KB cho cùng khung hình đó). Ở quality=10 -- nhiễu nhiều nhưng vẫn nhận ra -- tỷ lệ nén đạt 30 đến 50 lần (12 đến 20 KB).
Những con số đó quyết định những gì có thể thực hiện với các khung hình đã lưu. Một đường dẫn thẻ SD duy trì 10 MB/s có thể xử lý 30 khung hình mỗi giây của nội dung VGA được mã hóa JPEG ở quality=50 với chỗ dư (khoảng 1 đến 2 MB/s); lưu cùng nội dung không nén đòi hỏi hơn 18 MB/s, vượt quá những gì đường dẫn hệ thống tệp của camera có thể duy trì lên thẻ. Một máy chủ USB kéo các khung hình được mã hóa JPEG qua CDC ở tốc độ 1 MB/s nhận các khung hình 30 đến 60 KB ở khoảng 15 đến 30 khung hình mỗi giây; kéo các khung hình thô ở cùng tốc độ chỉ nhận được một hoặc hai khung hình mỗi giây.
Tóm lại: các phương thức nén không chỉ là tiện ích để lưu. Chúng là thứ khiến cho khung hình đã chụp có thể sử dụng được bên ngoài camera ở tốc độ khung hình mà ứng dụng quan tâm. Chọn đúng mức nén -- JPEG chất lượng 50 cho ghi log thông thường, 80 cho công việc chất lượng cao, PNG cho chụp đường nét -- là một phần công việc thường xuyên của bất kỳ ứng dụng camera không tầm thường nào.