5.16. Bộ nhân tích chập tùy chỉnh¶
Các bộ lọc lân cận đã đề cập từ trước đều có một thống kê tích hợp sẵn mà bộ lọc áp dụng cho cửa sổ tại mỗi vị trí -- trung bình, trung bình có trọng số Gaussian, trung vị. morph() là bộ lọc duy nhất cho phép ứng dụng tự cung cấp thống kê đó, dưới dạng một bộ nhân: một ma trận trọng số nhỏ mô tả cách bộ lọc kết hợp các điểm ảnh lân cận thành một giá trị đầu ra duy nhất.
Cơ chế này là phép toán tích chập cổ điển. Tại mỗi vị trí đầu ra, mỗi điểm ảnh lân cận được nhân với trọng số tương ứng trong bộ nhân, các tích được cộng lại, kết quả tùy chọn được nhân tỉ lệ và bù đắp, rồi giá trị được ghi vào điểm ảnh đầu ra. Các bộ nhân khác nhau tạo ra kết quả khác nhau từ cùng một đầu vào. Bộ nhân với tất cả trọng số dương bằng nhau sẽ tái tạo bộ lọc mean(); bộ nhân hình chuông tái tạo gaussian(). Các mẫu vượt ra ngoài đó tạo ra phản hồi cạnh, hiệu ứng nổi, gradient, làm sắc nét, làm mờ chuyển động, và một danh mục dài các hiệu ứng khác -- tất cả mọi thứ mà xử lý ảnh cổ điển từng muốn thực hiện trong một lần quét tuyến tính duy nhất.
5.16.1. Phương thức morph¶
Cú pháp tương tự như các bộ lọc lân cận khác nhưng có thêm một đối số:
img.morph(size, kernel, mul=1.0, add=0.0)
size là bán kính tương tự như ở các nơi khác, vì vậy bộ nhân phải có chính xác (2 * size + 1) hàng nhân (2 * size + 1) cột. Bản thân bộ nhân là một danh sách Python phẳng gồm nhiều số đó, theo thứ tự theo hàng -- (2 * size + 1) mục đầu tiên là hàng trên cùng, tiếp theo (2 * size + 1) là hàng thứ hai, và cứ thế cho đến hàng dưới cùng. mul nhân tỉ lệ tổng tích trước khi ghi vào điểm ảnh đầu ra, và add cộng thêm một hằng số. Mặc định mul=1.0 và add=0.0 giữ nguyên đầu ra tích chập.
Một chi tiết đáng chú ý: phương thức tự động chia tổng tích cho tổng các giá trị trong bộ nhân trước khi ghi đầu ra. Phép chia tự động này có nghĩa là một bộ nhân trung bình có các mục tổng bằng chín -- ví dụ, bộ lọc hộp 3×3 -- đầu ra có tỉ lệ một phần chín mà không cần thêm công sức, và một bộ nhân xấp xỉ Gaussian có tổng bằng mười sáu đầu ra có tỉ lệ một phần mười sáu, cả hai đều không cần ứng dụng tự tính phép chia. Ứng dụng chỉ đặt mul khi muốn thêm tỉ lệ ngoài việc chuẩn hóa tự động -- hoặc, phổ biến hơn, khi tổng bộ nhân bằng không (bộ nhân phản hồi cạnh) và phép chia tự động sẽ là phép chia cho không. Framework coi tổng là một trong trường hợp đó, và mul trở thành điều chỉnh duy nhất để giữ tổng tích chưa chia trong phạm vi.
Cặp threshold=True / offset=N từ phần ngưỡng thích ứng cũng hoạt động trên morph(), vì vậy cùng framework bộ nhân tùy chỉnh có thể tạo ra ngưỡng nhị phân có ngưỡng cắt được tính bằng thống kê tùy chỉnh.
5.16.2. Bố cục bộ nhân¶
Bộ nhân 3×3 (size=1) là một danh sách phẳng gồm chín số được sắp xếp từ trái sang phải, từ trên xuống dưới. Quy ước này dễ đọc nếu danh sách được viết trải dài ba dòng Python:
sobel_x = [-1, 0, 1,
-2, 0, 2,
-1, 0, 1]
Đây là toán tử gradient Sobel-x -- bộ nhân chuẩn đầu tiên mà bất kỳ ứng dụng nào cũng muốn dùng và là bộ nhân hữu ích để đi qua từng bước. Mẫu rất đơn giản: trọng số âm ở cột bên trái, trọng số dương ở cột bên phải, với cột giữa bằng không. Trọng số hàng -1, -2, -1 (hoặc 1, 2, 1 ở bên phải) cao hơn ở giữa so với các góc, điều này làm cho hàng giữa có ảnh hưởng lớn hơn đến kết quả so với các hàng góc.
Khi bộ nhân quét qua một cạnh dọc -- một cột điểm ảnh đi từ tối ở bên trái sang sáng ở bên phải -- các trọng số âm thu nhận phía tối và các trọng số dương thu nhận phía sáng. Tổng tích là một số dương lớn, mà bộ lọc ghi dưới dạng điểm ảnh đầu ra sáng. Một vùng nằm ngang có độ sáng đồng đều tạo ra kết quả bằng không, vì mỗi trọng số dương được kết hợp với một trọng số âm có cùng độ lớn trên một điểm ảnh có cùng giá trị.
Chạy bộ nhân:
img.morph(1, sobel_x, mul=0.25)
Bộ nhân Sobel có tổng bằng không -- mỗi trọng số âm ở bên trái được kết hợp với trọng số dương bằng nhau ở bên phải -- vì vậy phép chia tự động không chia cho bất kỳ thứ gì, và mul là tỉ lệ duy nhất trên tổng tích. mul=0.25 giữ phản hồi trong phạm vi: tổng tuyệt đối lớn nhất mà Sobel-x có thể tạo ra từ một vùng 3×3 là xấp xỉ 4 * 255 = 1020 (tám điểm ảnh sáng có trọng số lên đến 2), và chia xuống bốn đưa các trường hợp cực đoan về 255, nơi định dạng cắt bớt chúng một cách sạch sẽ.
Bộ nhân Sobel-y tương ứng phát hiện các cạnh ngang bằng cách xoay cùng mẫu trọng số 90 độ:
sobel_y = [-1, -2, -1,
0, 0, 0,
1, 2, 1]
Các ứng dụng muốn phát hiện bất kỳ cạnh nào, bất kể hướng, thường chạy cả hai Sobel và kết hợp các phản hồi.
5.16.3. Bù đắp đầu ra¶
add là nửa còn lại của câu chuyện tỉ lệ. Phản hồi của bộ nhân có tổng bằng không là có dấu -- dương ở một phía của cạnh, âm ở phía kia -- và nửa âm bị cắt về không khi ghi vào điểm ảnh không dấu. add=128 dịch chuyển phản hồi để tập trung ở mức xám trung bình, vì vậy các phản hồi âm tồn tại dưới dạng giá trị dưới 128 và các phản hồi dương nằm trên nó: phản hồi cạnh hoặc hiệu ứng nổi trở nên hiển thị ở cả hai hướng, với chi phí là nửa phạm vi mỗi phía.
Tổ hợp mul và add mà một bộ nhân yêu cầu là một phần trong thiết kế của bộ nhân; danh mục bộ nhân chuẩn liệt kê các cài đặt đúng cho mỗi bộ nhân phổ biến.
5.16.4. Bộ nhân lớn hơn¶
Mọi thứ trên trang này được mô tả với bộ nhân 3×3 (size=1), vì đó là kích thước danh mục chuẩn sử dụng và vì bố cục theo hàng dễ viết tay ở kích thước đó. Tuy nhiên, không có gì trong cơ chế giới hạn bộ nhân ở 3×3. size=2 chạy bộ nhân 5×5, với hai mươi lăm mục trong danh sách phẳng; size=3 chạy 7×7 với bốn mươi chín mục; và cứ thế, lên đến bất kỳ bán kính nào ứng dụng sẵn sàng trả chi phí. Framework xử lý cả bố cục danh sách phẳng và bố cục hàng lồng nhau ở bất kỳ kích thước lẻ nào.
Lý do để dùng bộ nhân lớn hơn cũng giống như lý do dùng lân cận lớn hơn trên bất kỳ bộ lọc tích hợp sẵn nào: trung bình nhiều hơn, phát hiện đặc trưng rộng hơn, ít nhạy cảm hơn với nhiễu điểm ảnh đơn. Chi phí tăng theo bình phương của bán kính -- bộ nhân 5×5 làm xấp xỉ 2,8 lần công việc trên mỗi điểm ảnh so với bộ nhân 3×3, bộ nhân 7×7 khoảng 5,4 lần -- và hệ số nhân đó trực tiếp làm giảm tốc độ khung hình.
Mẫu thực tế là giữ ở size=1 cho danh mục chuẩn và chỉ dùng kích thước lớn hơn khi thuật toán cần lân cận lớn hơn. Các bộ phát hiện cạnh hiếm khi được hưởng lợi vượt quá 3×3; bộ lọc làm mịn đôi khi có; kích thước phù hợp phụ thuộc vào tỉ lệ của các đặc trưng mà ứng dụng đang cố nhấn mạnh hoặc loại bỏ.
5.16.5. Khi nào nên dùng morph¶
Để làm mịn thông thường, mean(), gaussian(), và bilateral() nhanh hơn và sạch hơn. Để phát hiện cạnh, laplacian() và find_edges() được xây dựng chuyên dụng. Trường hợp dùng trực tiếp morph() là khi ứng dụng cần một phép tích chập cụ thể mà các bộ lọc tích hợp sẵn không cung cấp -- Sobel theo hướng, mẫu cạnh tùy chỉnh, bộ nhân được tinh chỉnh cho một kết cấu cụ thể mà phần còn lại của pipeline sẽ tìm kiếm, hoặc bất kỳ bộ nhân nào trong danh mục chuẩn mà xử lý ảnh cổ điển đã xây dựng qua nhiều thập kỷ. Toàn bộ sự linh hoạt của các bộ nhân tùy ý đều có sẵn; cái giá là ứng dụng phải chịu trách nhiệm chọn các giá trị bộ nhân để tạo ra kết quả mong muốn.