5.3. Định dạng điểm ảnh

Một thuật toán phát hiện cạnh yêu cầu mỗi điểm ảnh lưu trữ một giá trị độ sáng. Một thuật toán theo dõi đối tượng có màu yêu cầu mỗi điểm ảnh mang thông tin màu sắc. Một thuật toán thực hiện phép đóng hình thái học yêu cầu mỗi điểm ảnh chỉ có giá trị bật hoặc tắt. Định dạng điểm ảnh mà Image lưu giữ -- một trong số các định dạng được liệt kê trong danh mục cảm biến thị giác -- chính là điều cho phép kiểm tra trước các yêu cầu đó: định dạng nói lên từ trước rằng điểm ảnh đang ở dạng nào, và từ đó các thuật toán nào có thể chạy trên chúng mà không cần bước chuyển đổi.

Trang này trình bày cách ràng buộc đó thể hiện trong thực tế. Định dạng nào là lựa chọn phù hợp phụ thuộc vào những gì pipeline sẽ thực hiện, và các phương thức chuyển đổi giữa các định dạng là cách một pipeline cần dùng nhiều hơn một định dạng kết nối các giai đoạn lại với nhau.

A vertical stack of five labelled byte-layout strips. BINARY shows one byte split into eight single-bit cells, marked "8 pixels per byte". GRAYSCALE shows three labelled single-byte cells each marked "1 pixel". RGB565 shows two adjacent bytes with bit fields RRRRR GGGGGG BBBBB labelled "1 pixel". YUV422 shows four labelled byte cells Y0, U, Y1, V marked "2 pixels". BAYER shows two rows of four labelled byte cells: R G R G on the top row, G B G B on the bottom row.

Năm định dạng điểm ảnh không nén và cách các byte của chúng được đóng gói. JPEG và PNG không được vẽ ở đây vì chúng là các luồng nén có độ dài thay đổi thay vì các lưới điểm ảnh có kích thước cố định.

5.3.1. Định dạng thang xám chủ lực

Phần lớn thị giác máy cổ điển đều xoay quanh việc làm việc với các giá trị độ sáng. Phát hiện cạnh, so khớp mẫu, giải mã AprilTag, ước lượng optical-flow, các toán tử hình thái học, phân tích vùng màu (blob) -- tất cả chúng, ở cấp độ mà các thuật toán hoạt động, đều xem xét độ sáng của mỗi điểm ảnh và cách độ sáng đó so sánh với độ sáng của các điểm ảnh lân cận. Màu sắc của cảnh thường hữu ích cho ứng dụng gọi chúng, nhưng bản thân các thuật toán không cần đến nó.

Định dạng thang xám cung cấp cho các thuật toán chính xác điều đó, không có chi phí thêm. Một byte mỗi điểm ảnh lưu trữ giá trị độ sáng từ 0 (đen) đến 255 (trắng). Định dạng này có kích thước bằng một nửa RGB565 và YUV422 và bằng một phần ba RGB888, vì vậy mọi thao tác đều xử lý ít dữ liệu hơn -- vừa nhanh hơn vừa ít áp lực bộ đệm hơn. Trên các cam nhỏ hơn, nơi bộ đệm khung hình cạnh tranh với phần còn lại của tập lệnh về RAM, sự khác biệt về bộ nhớ chiếm dụng có thể quyết định liệu một pipeline có thể chạy được hay không. Nếu màu sắc không phải là tín hiệu mà thuật toán cần, thang xám là câu trả lời đúng.

5.3.2. Màu sắc qua RGB565

Khi màu sắc tín hiệu cần thiết -- theo dõi một điểm đánh dấu có màu, phân biệt táo đỏ với táo xanh, nhận dạng phần tử UI theo màu sắc -- hai byte mỗi điểm ảnh cung cấp đủ thông tin màu sắc cho các loại phân loại mà các thuật toán thực hiện. RGB565 là định dạng màu mặc định trên cam, và là định dạng mà các phương thức nhận biết màu trên bề mặt mong đợi.

Hiển thị một khung hình có chú thích -- vẽ hộp phát hiện, viết văn bản chẩn đoán, đưa khung hình lên màn hình hoặc gửi đến trình xem từ xa -- cũng tự nhiên sử dụng RGB565. Bản xem trước IDE, các bộ điều khiển hiển thị trên bo mạch, và hầu hết các đích đến mạng đều tiêu thụ định dạng này trực tiếp hoặc chuyển đổi từ nó với chi phí thấp.

5.3.3. Bayer làm định dạng lưu trữ

Một ảnh Bayer là đầu ra thô của cảm biến, trước khi ISP debayer nó thành một biểu diễn màu sắc hoàn chỉnh. Mỗi điểm ảnh là một byte lưu trữ một kênh màu duy nhất -- kênh mà bộ lọc màu tại vị trí đó trong mô-đun mosaic đã truyền qua. Điều này khiến ảnh Bayer có cùng kích thước với ảnh thang xám và bằng một phần ba RGB888, phù hợp với mục đích thực sự hữu ích của Bayer: lưu trữ nhiều khung hình cùng lúc khi RAM là ràng buộc chính.

Hạn chế là các thuật toán trong module image không hoạt động trực tiếp trên ảnh Bayer. Khi chưa được debayer, không có điểm ảnh nào mang đủ thông tin để đưa ra đánh giá màu sắc riêng lẻ, và các mẫu mà các thuật toán tìm kiếm -- cạnh, góc, vùng màu (blob) -- sẽ bị biến dạng bởi mosaic. Các cách duy nhất để đọc hoặc sửa đổi ảnh Bayer là get_pixel()set_pixel(); tất cả những gì còn lại đều yêu cầu một biểu diễn hoàn chỉnh.

Mẫu thường gặp là lưu trữ các khung hình dưới dạng Bayer khi chúng cần xếp hàng và chuyển đổi từng khung hình sang thang xám hoặc RGB565 vào thời điểm quá trình xử lý thực sự bắt đầu. Việc chuyển đổi tốn CPU cycles nhưng tiết kiệm RAM vốn sẽ bị chiếm dụng để giữ các khung hình hoàn chỉnh trong suốt vòng đời của ứng dụng.

Ghi chú

Các thao tác duy nhất của module image trên điểm ảnh Bayer trực tiếp là get_pixel(), set_pixel(), và đường dẫn mã hóa JPEG cung cấp cho bản xem trước IDE hoặc trình xem từ xa. Vẽ, phân tích và lọc đều yêu cầu chuyển đổi sang thang xám, RGB565 hoặc binary trước.

5.3.4. YUV422 cho các pipeline cần cả hai

YUV422 tách thông tin của mỗi điểm ảnh thành một kênh luminance (Y) và hai kênh chrominance (U và V), đồng thời lấy mẫu giảm chrominance để các cặp điểm ảnh liền kề dùng chung một U và một V. Số byte trung bình mỗi điểm ảnh là hai -- giống như RGB565 -- nhưng được bố cục sao cho kênh Y đã là một ảnh thang xám 8 bit liên tục nằm ở các offset đã biết trong bộ đệm.

Bố cục đó chính xác là điều một pipeline muốn khi một số giai đoạn của nó xử lý thang xám và một số giai đoạn cần màu sắc. Đọc trực tiếp các giá trị Y cho các giai đoạn thang xám bỏ qua chi phí chuyển đổi tường minh; các kênh U và V có mặt khi một giai đoạn sau thực sự cần màu sắc. Ngoài mẫu cụ thể đó, RGB565 thường là lựa chọn đơn giản hơn cho màu sắc và thang xám là lựa chọn đơn giản hơn cho công việc chỉ dùng độ sáng -- giá trị của YUV422 đến từ việc làm tốt cả hai cùng lúc.

Ghi chú

Module image hoạt động trên YUV422 theo cách hạn chế hơn so với thang xám, RGB565 hoặc binary -- đọc trực tiếp kênh Y cho công việc thang xám và đường dẫn mã hóa JPEG cung cấp cho bản xem trước IDE hoặc trình xem từ xa. Các phương thức nhận biết màu yêu cầu RGB565; các khung hình YUV422 cần được chuyển đổi tường minh trước khi phân tích màu sắc hoặc vẽ.

5.3.5. Binary, mặt nạ, và đầu ra phân ngưỡng

Ảnh binary là một bit mỗi điểm ảnh: mỗi điểm ảnh là 0 hoặc 1. Định dạng này hiếm khi xuất hiện như một ảnh chụp từ cảm biến; thay vào đó nó xuất hiện như đầu ra tự nhiên của phân ngưỡng (nơi một bài kiểm tra màu sắc hoặc độ sáng phân loại mỗi điểm ảnh thành "khớp" hoặc "không khớp") và là đầu vào tự nhiên cho các phép toán hình thái học và đối số mask mà nhiều phương thức chấp nhận.

Ưu điểm thực tế của định dạng này là kích thước nhỏ. Một ảnh binary chiếm một phần tám diện tích bộ nhớ của ảnh thang xám, nên việc mang theo một mặt nạ lớn -- lựa chọn theo từng điểm ảnh về vị trí nào mà một thao tác downstream nên tác động -- là rất rẻ. Việc nhiều thao tác chấp nhận ảnh binary như đối số từ khóa mask= là mặt khác của cùng một điểm: định dạng nhỏ gọn, và việc kết nối đầu ra binary của một giai đoạn với đầu vào mặt nạ của giai đoạn khác là một mẫu pipeline phổ biến.

5.3.6. JPEG và PNG tại ranh giới

Các đối tượng Image JPEG và PNG khác với các đối tượng khác trong danh mục. Chúng không phải là lưới điểm ảnh; chúng là các luồng byte nén mã hóa dữ liệu điểm ảnh ở dạng mà các thao tác ở cấp điểm ảnh không thể đọc. Gọi get_pixel() trên một JPEG không trả về điểm ảnh tại một vị trí; điểm ảnh không nằm ở đâu trong bộ đệm ở dạng đã giải nén để phương thức có thể lấy.

JPEG và PNG xuất hiện tại ranh giới của xử lý ảnh, nơi dữ liệu điểm ảnh đang rời khỏi hoặc đến cam ở dạng nén. Lưu một khung hình vào đĩa dưới dạng JPEG giữ cho tệp nhỏ gọn; gửi một khung hình qua mạng dưới dạng JPEG giữ cho việc truyền dẫn ít tốn kém; tải một khung hình tham chiếu từ tệp JPEG cho phép nó nằm trên đĩa ở dạng nhỏ hơn nhiều so với các điểm ảnh thô. Đối với bất kỳ trường hợp sử dụng nào trong số đó, biểu diễn nén là câu trả lời đúng. Tuy nhiên, để thực hiện bất kỳ xử lý thực sự nào trên JPEG, ứng dụng phải chuyển đổi nó sang định dạng có thể làm việc trước -- và đó là nơi các byte nén được giải mã thành điểm ảnh và nơi sự phình to của bộ đệm (một JPEG 30 KB có thể trở thành 600 KB RGB565) thực sự xảy ra.

5.3.7. Chuyển đổi giữa các định dạng

Đường dẫn chuyển đổi là thứ kết nối các định dạng khác nhau thành một pipeline duy nhất. Năm phương thức trên lớp Image nhận một ảnh hiện có và trả về một ảnh mới ở định dạng khác:

  • to_grayscale() tạo ra một ảnh một byte mỗi điểm ảnh, định dạng mà các thuật toán cổ điển yêu cầu.

  • to_rgb565() tạo ra định dạng màu hai byte mỗi điểm ảnh mà các phương thức nhận biết màu và bản xem trước IDE đều sử dụng.

  • to_bitmap() tạo ra một ảnh binary một bit, định dạng mà hình thái học và đối số mask chấp nhận.

  • to_jpeg() tạo ra một ảnh nén JPEG phù hợp để lưu hoặc truyền dẫn.

  • to_png() tạo ra một ảnh nén PNG khi mã hóa không mất dữ liệu được ưu tiên hơn các tệp nhỏ hơn của JPEG.

Mỗi lần chuyển đổi chạy tại chỗ theo mặc định: bộ đệm của ảnh nguồn bị ghi đè bằng kết quả đã chuyển đổi, và các điểm ảnh gốc của nguồn sẽ biến mất sau khi lời gọi trả về. Đây là tùy chọn rẻ nhất cả về CPU lẫn bộ nhớ, và là câu trả lời đúng khi khung hình nguồn sẽ không cần dùng cho bất kỳ mục đích nào khác.

Khi nguồn vẫn cần thiết -- khi một giai đoạn sau của pipeline phải xem khung hình gốc -- hai đối số từ khóa ghi đè mặc định tại chỗ. copy=True cấp phát một bộ đệm riêng cho ảnh đã chuyển đổi trên Python heap và giữ nguyên nguồn. copy_to_fb=True cũng cấp phát tương tự nhưng đặt nó vào bộ đệm khung hình thay vì heap -- đây là điều một ứng dụng sử dụng khi ảnh đã chuyển đổi cần xuất hiện trong bản xem trước IDE, vì IDE đọc từ bộ đệm khung hình.

Hai phương thức bổ sung tạo ra ảnh RGB565 được tô màu thông qua bảng màu thay vì bằng chuyển đổi thẳng. to_rainbow() ánh xạ mỗi giá trị đầu vào một kênh sang màu dọc theo gradient mượt mà chạy qua quang phổ nhìn thấy. to_ironbow() ánh xạ mỗi giá trị đầu vào sang bảng màu máy ảnh nhiệt phi tuyến chạy từ đen qua đỏ đậm và cam đến trắng. Cả hai đều là công cụ trực quan hóa hơn là công cụ đo lường; mục đích là làm cho ảnh một kênh mà các giá trị thô của nó sẽ không nhìn thấy được trở nên dễ đọc hơn trong nháy mắt.

5.3.8. Kích thước bộ đệm

Một chi tiết cuối cùng về định dạng đáng được làm rõ. size() luôn báo cáo kích thước bộ đệm byte, không phải số lượng điểm ảnh. Đối với các định dạng không nén, điều này suy ra trực tiếp từ kích thước và byte-mỗi-điểm-ảnh: width * height * bytes_per_pixel. Đối với JPEG và PNG, đây là kích thước của luồng nén, thay đổi từ khung hình này sang khung hình khác tùy thuộc vào nội dung cảnh. Mã cấp phát bộ đệm từ ngân sách byte sử dụng size() cho trường hợp trước; mã truyền các khung hình nén ra khỏi cam đọc nó sau mỗi lần nén để biết luồng thực sự chứa bao nhiêu byte.