Làm quen với Histogram trong OpenCV

Công nghệ

Đến với bài viết này, chúng ta sẽ làm quen với một khái niệm mới là histograms – biểu đồ. Vậy histograms là gì? Đây là một dạng biểu đồ thể hiện tần suất, nó mô tả một cách đơn giản mà không làm mất bất cứ thống kê thông tin nào của dữ liệu. Trong xử lý ảnh, biểu đồ này biểu thị cường độ phân bố điểm ảnh (cho dù là ảnh màu hay ảnh xám). Biểu đồ có thể được biểu diễn dạng đường hoặc dạng cột, nhờ đó mà nó cung cấp hình ảnh trực quan về mức độ phân bố giá trị điểm ảnh. Ngoài ra, histograms còn chứa các thông tin về kiểu phân bố dữ liệu, độ rộng dữ liệu, đánh giá tính đối xứng của dữ liệu và cho thấy dữ liệu nằm ngoài phân bố. Trong phạm vi bài học này, chúng ta sử dụng không gian màu RGB, do đó giá trị điểm ảnh sẽ nằm trong khoảng từ 0 tới 255.

Bằng cách vẽ biểu đồ, ta hãy tưởng tượng trục x như là các ngăn chứa khác nhau. Nếu chúng ta vẽ biểu đồ có 256 ngăn chứa, thì chúng ta đang xếp lần lượt từng giá trị điểm ảnh vào một ngăn tương ứng và trục y sẽ cho ta biết số lần xuất hiện của từng giá trị trong mỗi ngăn xếp. Ngoài ra nếu chúng ta chỉ chia thành 2 ngăn xếp thì các giá trị có thể được xếp vào hai ngăn tương ứng [0,128) hoặc [128,255]. Cuối cùng thì số lượng trong mỗi ngăn xếp sau đó được biểu thị trên trục y. Có thể bạn đọc vẫn đang đặt ra câu hỏi rằng tại sao chúng ta cần nắm được ý nghĩa của biểu đồ trong xử lý ảnh. Khi ta phân tích histograms – biểu đồ phân bố điểm ảnh, ta sẽ có được các thông tin về độ tương phản, độ sáng và cường độ phân bố các giá trị

Chúng ta sẽ cùng nhau bắt tay vào tạo một biểu đồ với bức ảnh yêu thích và phân tích xem có thể thu thập được những thông tin hay ho nào. 

Để có thể tạo một biểu đồ của bức ảnh với opencv, ta sử dụng hàm cv2.calcHist, trong đó:

cv2.calcHist(images, channels, mask, histSize, ranges):

a.images: bức ảnh ta muốn tạo biểu đồ

b.channels: danh sách các chỉ mục tính biểu đồ. Giá trị là 0 với ảnh xám. Với ảnh màu, giá trị là [0,1,2] tương ứng R, G, và B

c.mask: khái niệm mặt nạ bạn đọc đã được tìm hiểu trong bài học trước, khi cung cấp tham số này, biểu đồ sẽ chỉ hiển thị giá trị nằm trong mặt nạ định nghĩa. Nếu không muốn sử dụng mặt nạ thì ta để là None

d.histSize: số lượng ngăn xếp ta muốn sử dụng để tính toán biểu đồ. Tham số này là một list – danh sách, và một cho mỗi kênh màu chúng ta thực hiện tính toán, và không nhất thiết các tham số phải bằng nhau. Ví dụ mình muốn tạo biểu đồ kích thước 32 ngăn xếp cho mỗi kênh màu thì sẽ khai báo là [32,32,32]

e.ranges: tại đây chúng ta sẽ chỉ định phạm vi có thể của điểm ảnh. Thông thường, giá trị này trong khoảng [0,256] cho mỗi kênh màu, tuy nhiên nếu bạn sử dụng hệ màu khác ngoài RGB (như HSV chẳng hạn), thì khoảng giá trị này sẽ có chút khác biệt. 

Sau đây, ta sẽ cùng nhau tìm hiểu cách tạo biểu đồ với ảnh xám và ảnh màu.

1.Tạo biểu đồ ảnh xám

Dòng 1-14, chúng ta thực hiện thao tác tương tự như các bài học trước: khai báo thư viện, định nghĩa tham số, đọc ảnh truyền vào và hiển thị bức ảnh. Tuy nhiên, điểm khác biệt dòng 12, ta thực hiện chuyển đổi bức ảnh truyền vào từ ảnh màu thành ảnh xám, và chúng ta sẽ tìm hiểu thêm một thư viện mới là matplotlib – đây là một thư viện hỗ trợ mạnh trong quá trình vẽ đồ thị. Ở dòng 16, chúng ta bắt tay vào tính biểu đồ qua sử dụng hàm cv2.calcHist. Như bạn đọc có thể thấy, tham số đầu tiên là biến số rs_img chứa bức ảnh xám. Do ảnh xám có duy nhất một kênh màu, do đó tham số thứ hai mà ta truyền vào là [0] kênh màu. Ngoài ra, chúng ta không sử dụng mặt nạ nên tham số tiếp theo truyền vào hàm là None. Tiếp đến là chúng ta sử dụng 256 ngăn chứa – 256 nhóm giá trị và khoảng giá trị từ 0 tới 256 phù hợp.

Cuối cùng, ta gọi plt.plot() để hiển thị biểu đồ và kết quả cuối cùng bạn đọc sẽ tìm thấy phía dưới đây. Biểu đồ cho thấy giá trị điểm ảnh tập trung trong khoảng từ 0 tới 50.

2.Tạo biểu đồ ảnh màu

Ở ví dụ trên, chúng ta đã nắm được cách tạo biểu đồ ảnh xám, bây giờ bạn đọc và tôi sẽ tìm hiểu cách tính phân bố điểm ảnh cho từng kênh màu trong ảnh

Ở đoạn mã nguồn này chúng ta cần để ý những đoạn sau:

Điều đầu tiên khi ta muốn tạo biểu đồ phân bố giá trị điểm ảnh ở ảnh màu chính là phân tách giá trị ảnh màu ra thành 3 kênh màu: b, g và r – dòng 16. Thông thường, chúng ta đọc hệ màu RGB theo thứ tự lần lượt là r,g và b. Tuy nhiên, opencv lưu ảnh ở dạng mảng numpy theo thứ tự ngược lại: BGR – bạn đọc nên chú ý thứ tự này để có thể xử lý chính xác. Tiếp đó, ta tạo biến colors lưu giá trị các màu biểu thị ở dòng 17.  

Dòng 18-21, khởi tạo và hiển thị biểu đồ với chú thích tương ứng.

Tiếp đó, dòng 23, ta thực hiện vòng lặp chạy qua từng kênh màu. Với mỗi kênh màu, chúng ta tính giá trị hiển thị lên biểu đồ theo phân bố giá trị điểm ảnh tương ứng. Kết quả biểu đồ phân bố thu được như sau: 

Như vậy, chúng ta đã có thể tính biểu đồ lần lượt qua từng kênh màu. Bây giờ, chúng ta sẽ chuyển qua biểu đồ đa chiều và xem xét tính toán hai kênh màu một lúc. Ví dụ, bạn đọc có thể có các câu hỏi như là “Có bao nhiêu điểm ảnh có giá trị đỏ là 10 và xanh là 30?”. Bạn đọc theo dõi minh họa sau:

Bạn đọc có thể thấy rằng mã nguồn ví dụ này khá là dài, tuy nhiên đây là do chúng ta đang thực hiện tính toán biểu đồ màu sắc 2D cho mỗi kết hợp của kênh màu RGB: B&R, G&R, G&B. Như vậy, bây giờ chúng ta đang làm việc với biểu đồ đa chiều. Ở ví dụ trước, mình sử dụng 256 nhóm để làm ví dụ. Tuy nhiên, nếu chúng ta sử dụng 256 nhóm để biểu diễn biểu đồ 2 chiều thì chúng ta sẽ thu về một biểu đề có số lượng 256×256=65536 điểm ảnh riêng biệt. Điều này gây lãng phí bộ nhớ và không thực tiễn. Phần lớn các ứng dụng sử dụng trong khoảng 8 tới 64 nhóm để tính biểu đồ đa chiều. Dòng 32-33, mình sử dụng 32 nhóm thay vì 256. Bạn đọc có thể dễ dàng thấy rằng tham số đầu tiên cho hàm cv2.calcHist nhận vào mảng gồm giá trị hai kênh màu là G và B, hai tổ hợp còn lại cách triển khai tương tự. Vậy biểu đồ 2D được opencv xử lý như thế nào? Khá đơn giản, đó chỉ là mảng numpy hai chiều. Do đó, khi ta chọn cách chia thành 32 nhóm, ta thu được biểu đồ 32×32. Cách biểu đồ hiển thị trực quan bạn đọc có thể tham khảo ở kết quả dưới đây. Đầu tiên là biểu đồ gồm hai kênh màu G và B, tiếp đó là G và R, cuối cùng là B và R. Sắc thái xanh lam biểu diễn điểm ảnh có giá trị nhỏ, còn sắc đỏ biểu thị điểm ảnh có giá trị lớn

Cuối cùng, chúng ta tiếp tục xây dựng một biểu đồ 3 chiều kết hợp cả 3 kênh màu RGB:

Đoạn mã khá đơn giản sau khi chúng ta mở rộng mã từ các phần trên. Dòng 58, ta tính biểu đồ 8x8x8 ở mỗi kênh màu.

3.Cân bằng biểu đồ

Cân bằng biểu đồ là phương pháp cải thiện độ tương phản của bức ảnh bằng cách kéo dãn phân bố điểm ảnh. Bạn đọc có thể thấy ở các ví dụ trên, phân bố điểm ảnh không đồng đều là tập trung chủ yếu ở một số đỉnh có giá trị nhất định. Bằng việc kéo dãn phân bố giá trị này, bức ảnh có thể trở nên sáng hơn. Phương pháp này rất hữu ích khi bức ảnh có nền và trung tâm đều sáng hoặc đều tối – điều mà gây ra cách hiệu ứng xấu trong nhiếp ảnh. Ngoài ra, phương pháp này còn được sử dụng rộng rãi với đối tượng là ảnh vệ tinh và ảnh chụp y học. Trước tiên, chúng ta sẽ cùng nhau tham khảo ví dụ sau:

Dòng 1- 13, tương tự các ví dụ trước, nhưng ở dòng 12, chúng ta thực hiện biến đổi ảnh màu đầu vào về ảnh xám. Phương pháp cân bằng biểu đồ được thực hiện ở dòng 15 qua hàm hỗ trợ cv2.equalizeHist từ opencv với tham số nhận vào duy nhất là biến số được định nghĩa chứa ảnh xám. Kết quả đạt được với ảnh gốc chuyển xám ở phía bên trái, và ảnh được áp dụng phương pháp ở bên phải

Sau khi kéo dãn giá trị bằng phương pháp cân bằng đồ thị, bức ảnh trở nên sáng hơn – ảnh phải.

4.Biểu đồ và mặt nạ

Ở bài học trước, mặt nạ có thể được sử dụng để tập trung vào một vùng trên ảnh mà chúng ta quan tâm. Bây giờ, chúng ta sẽ xây dựng một mặt nạ và tính toán biểu đồ cho vùng ảnh áp dụng mặt nạ

 

Dòng 1-4, chúng ta khai báo các thư viện cần sử dụng. Dòng 6, ta định nghĩa hàm plot_histogram nhận vào 3 tham số là img, title và mask, trong đó img: ảnh, title: tên khung và mask: mặt nạ với giá trị mặc định là None. Phần thân hàm chúng ta đơn giản là thực hiện vẽ biểu đồ như các ví dụ trên cho mỗi kênh màu

Dòng 21-29, ta định nghĩa tham số truyền vào, đọc ảnh và hiển thị ảnh gốc. Sau đó, chúng ta gọi hàm plot_histogram để hiển thị biểu đồ của bức ảnh. Kết quả thu được:

Sau đó, ta xây dựng mặt nạ bằng cách định nghĩa ma trận giá trị 0 với numpy có cùng kích thước với bức ảnh xử lý – dòng 32. Sau đó, chúng ta vẽ đa giác với tọa độ bắt đầu là (15,15) tới tọa độ kết thúc là (130, 100) ở dòng 33. Đa giác này sẽ được sử dụng làm mặt nạ, lấy ra giáp vai của gundam. Để có thể biểu diễn và áp dụng mặt nạ này, ta thực hiện phép AND.

Chúng ta thấy rằng, ảnh góc trên cùng bên phải là mặt nạ sử dụng và giáp vai thu được nằm ở phía dưới bên phải. Cuối cùng, chúng ta đánh giá biểu đồ sau khi áp dụng mặt nạ

Bằng cách áp dụng mặt nạ, chúng ta có thể áp dụng tính toán vào vùng mà ta mong muốn trên bức ảnh, ví dụ như chúng ta muốn phân tích phân bố màu sắc trên giáp vai của gundam chẳng hạn. Như vậy, sau bài học này, bạn đọc đã có cái nhìn toàn cảnh về những hỗ trợ biểu đồ mà opencv đem lại. Đây là một chủ đề khó và có ứng dụng rộng rãi. Chúng ta sẽ còn nhắc lại nội dung ở đây trong những bài học tiếp theo.