Phần 8. Các không gian màu trong OpenCV

0
9455
color

Trong bài viết này, chúng ta sẽ tìm hiểu về các không gian màu phổ biến được sử dụng trong Thị giác máy tính và áp dụng vào phân đoạn ảnh dựa trên màu sắc. Bài viết gồm các nội dung chính sau:

1. Không gian màu RGB

2. Không gian màu LAB

3. Không gian màu YCrCb

4. Không gian màu HSV

5. Áp dụng không gian màu vào phân đoạn ảnh

6. Phân tích dữ liệu để nâng cao chất lượng độ phân giải

Trong bài viết sẽ không mô tả lý thuyết các không gian màu vì có thể dễ dàng tìm hiểu chúng trên Wikipedia. Thay vào đó, bài viết sẽ giới thiệu các ví dụ cụ thể (Python và C++) và tìm hiểu một số đặc tính quan trọng của các không gian màu mà có thể lợi dụng trong các bài toán xử lý ảnh. 2 hình ảnh của cùng một khối lập phương nhưng trong điều kiện chụp khác nhau sẽ được sử dụng làm ảnh gốc trong bài viết. Mặc định 2 ảnh có định dạng BGR. Chúng ta có thể chuyển đổi giữa các không gian màu khác nhau bằng cách sử dụng hàm cvtColor() mà sẽ được giới thiệu cụ thể trong các ví dụ.

Python

bright = cv2.imread('cube1.jpg')
dark = cv2.imread('cube8.jpg')

C++

bright = cv::imread('cube1.jpg')
dark = cv::imread('cube8.jpg')

Hình ảnh đầu tiên được chụp trong điều kiện ngoài trời bị ánh nắng chói, trong khi hình ảnh thứ hai được chụp trong nhà với điều kiện ánh sáng bình thường.

1. Không gian màu RGB

Không gian màu RGB có các thuộc tính sau:

– Là một không gian màu kết hợp khi mà tất cả các màu thu được bằng sự kết hợp tuyến tính của các giá trị Đỏ, Xanh lục và Xanh lam.

– Ba kênh tương quan với nhau bởi lượng ánh sáng chiếu vào bề mặt.

Tiếp theo sẽ chia hai hình ảnh thành từng thành phần R, G và B và quan sát kết quả để hiểu hơn về không gian màu RGB.

Các kênh khác nhau Xanh lam (B), Xanh lục (G), Đỏ (R) của không gian màu RGB được hiển thị riêng biệt

Quan sát kênh B, có thể thấy rằng mặt màu xanh và trắng trông giống nhau trong điều kiện ánh sáng trong nhà nhưng khác biệt rõ ràng với điều kiện ngoài trời. Sự không đồng nhất này làm cho việc phân đoạn ảnh dựa trên màu sắc rất khó khăn trong không gian màu này. Một vấn đề của không gian màu RGB là sự trộn lẫn thông tin của từng kênh gồm thông tin liên quan đến màu sắc (sắc độ) thông tin liên quan đến cường độ (độ chói).

2. Không gian màu LAB

Không gian màu Lab có ba thành phần.

– L: Độ sáng (Cường độ).

– a: thành phần màu từ Xanh lục đến Đỏ tươi.

– b: thành phần màu từ Xanh lam đến Vàng.

Không gian màu Lab khá khác với không gian màu RGB. Trong không gian màu RGB, thông tin màu được tách thành ba kênh nhưng ba kênh giống nhau cũng mã hóa thông tin độ sáng. Trong khi đó không gian màu Lab, kênh L độc lập với thông tin màu và chỉ mã hóa độ sáng. Hai kênh còn lại mã hóa màu.

Không gian màu này có các thuộc tính sau:

– Không gian màu đồng nhất về mặt tri giác, gần đúng với cách chúng ta cảm nhận màu sắc.

– Độc lập với thiết bị (chụp hoặc hiển thị).

– Được sử dụng nhiều trong Adobe Photoshop.

– Có liên quan đến không gian màu RGB bằng một phương trình biến đổi phức tạp.

Quan sát hai hình ảnh trong không gian màu Lab được tách thành ba kênh

Python

brightLAB = cv2.cvtColor(bright, cv2.COLOR_BGR2LAB)
darkLAB = cv2.cvtColor(dark, cv2.COLOR_BGR2LAB)

C++

cv::cvtColor(bright, brightLAB, cv::COLOR_BGR2LAB);
cv::cvtColor(dark, darkLAB, cv::COLOR_BGR2LAB);

Nhận xét:

– Có thể thấy rằng sự thay đổi về độ chiếu sáng chủ yếu ảnh hưởng đến kênh L.

– Các kênh A và B chứa thông tin về màu sắc không có thay đổi lớn khi so sánh giữa điều kiện trong nhà hay ngoài sáng.

– Các giá trị tương ứng của Xanh lục, Cam và Đỏ (nằm trong dải của kênh A) không thay đổi trong kênh B và tương tự, các giá trị tương ứng của Xanh lam và Vàng (nằm trong dải của kênh B) không thay đổi trong kênh A.

3. Không gian màu YCrCb

Không gian màu YCrCb có nguồn gốc từ không gian màu RGB và có ba thành phần sau đây.

– Y: Độ chói hoặc thành phần Luma thu được từ RGB sau khi hiệu chỉnh gamma.

– Cr = R – Y (thành phần màu đỏ cách Luma bao xa).

– Cb = B – Y (thành phần màu xanh lam cách Luma bao xa).

Không gian màu này có các đặc tính sau:

– Tách các thành phần độ chói và độ sắc thành các kênh khác nhau.

– Chủ yếu được sử dụng trong nén (của các thành phần Cr và Cb) cho Truyền hình.

– Phụ thuộc vào thiết bị.

Python

brightYCB = cv2.cvtColor(bright, cv2.COLOR_BGR2YCrCb)
darkYCB = cv2.cvtColor(dark, cv2.COLOR_BGR2YCrCb)

C++

cv::cvtColor(bright, brightYCB, cv::COLOR_BGR2YCrCb);
cv::cvtColor(dark, darkYCB, cv::COLOR_BGR2YCrCb);

Quan sát hai hình ảnh gốc trong không gian màu YCrCb được tách thành các kênh riêng biệt:

Nhận xét:

– Khi thay đổi độ sáng giữa 2 hình thì các thành phần màu sắc và cường độ có đặc điểm như LAB.

– So với LAB, sự khác biệt giữa màu đỏ và cam của YCrCb là không rõ ràng.

– Màu trắng ở 3 kênh đều khác nhau.

4. Không gian màu HSV

Không gian màu HSV có ba thành phần sau

– H: Hue (Bước sóng chi phối).

– S: Độ bão hòa (Độ tinh khiết / sắc thái của màu).

– V: Giá trị (Cường độ).

Một số thuộc tính của HSV

– Chỉ sử dụng một kênh để mô tả màu sắc (H), dẫn đến rất trực quan để chỉ định màu sắc.

– Phụ thuộc vào thiết bị.

Python

brightHSV = cv2.cvtColor(bright, cv2.COLOR_BGR2HSV)
darkHSV = cv2.cvtColor(dark, cv2.COLOR_BGR2HSV)

C++

cv::cvtColor(bright, brightHSV, cv::COLOR_BGR2HSV);
cv::cvtColor(dark, darkHSV, cv::COLOR_BGR2HSV);

Các thành phần H, S và V của hai hình ảnh được hiển thị bên dưới:

Nhận xét:

– Kênh H rất giống nhau trong cả hai hình ảnh cho biết thông tin màu sắc vẫn còn nguyên vẹn ngay cả khi thay đổi ánh sáng.

– Thành phần S cũng rất giống nhau trong cả hai hình ảnh.

– Thành phần V phản ánh lượng ánh sáng chiếu vào nó, do đó nó thay đổi khi có sự thay đổi độ chiếu sáng.

– Có sự khác biệt lớn giữa các giá trị của mặt màu đỏ ở kênh H giữa 2 hình ảnh. Lý do là vì Hue được biểu diễn dưới dạng một vòng tròn và màu đỏ ở phần góc bắt đầu. Vì vậy, nó có thể nhận các giá trị từ [300, 360] và [0, 60].

5. Áp dụng không gian màu vào phân đoạn ảnh

Sau khi đã có một số nhận xét về các không gian màu trong OpenCV, chúng ta sẽ áp dụng những nhận xét đó để phát hiện màu Xanh lục từ khối lập phương.

Bước 1: Xác định các giá trị màu cho một màu cụ thể

Tìm khoảng giá trị gần đúng của màu xanh lục cho mỗi không gian màu. Để thuận tiện ta có thể tạo một GUI tương tác để có thể kiểm tra giá trị của tất cả các không gian màu cho mỗi pixel chỉ bằng cách di chuột vào hình ảnh như được hiển thị bên dưới.

Bước 2: Áp dụng ngưỡng cho phân đoạn ảnh

Trích xuất tất cả các pixel từ hình ảnh có giá trị gần với giá trị của pixel màu xanh lục. Có thể lấy phạm vi +/- 40 cho mỗi không gian màu và kiểm tra kết quả. Sử dụng hàm inRange để tìm mặt nạ của các pixel màu xanh lục và sau đó sử dụng phép toán bitwise_and để lấy các pixel màu xanh lục từ hình ảnh bằng cách sử dụng mặt nạ. Lưu ý để chuyển đổi một pixel sang không gian màu khác, trước tiên chúng ta cần chuyển mảng 1D thành mảng 3D.

Python

#python
bgr = [40, 158, 16]
thresh = 40

minBGR = np.array([bgr[0] - thresh, bgr[1] - thresh, bgr[2] - thresh])
maxBGR = np.array([bgr[0] + thresh, bgr[1] + thresh, bgr[2] + thresh])

maskBGR = cv2.inRange(bright,minBGR,maxBGR)
resultBGR = cv2.bitwise_and(bright, bright, mask = maskBGR)

#convert 1D array to 3D, then convert it to HSV and take the first element
# this will be same as shown in the above figure [65, 229, 158]
hsv = cv2.cvtColor( np.uint8([[bgr]] ), cv2.COLOR_BGR2HSV)[0][0]

minHSV = np.array([hsv[0] - thresh, hsv[1] - thresh, hsv[2] - thresh])
maxHSV = np.array([hsv[0] + thresh, hsv[1] + thresh, hsv[2] + thresh])

maskHSV = cv2.inRange(brightHSV, minHSV, maxHSV)
resultHSV = cv2.bitwise_and(brightHSV, brightHSV, mask = maskHSV)

#convert 1D array to 3D, then convert it to YCrCb and take the first element
ycb = cv2.cvtColor( np.uint8([[bgr]] ), cv2.COLOR_BGR2YCrCb)[0][0]

minYCB = np.array([ycb[0] - thresh, ycb[1] - thresh, ycb[2] - thresh])
maxYCB = np.array([ycb[0] + thresh, ycb[1] + thresh, ycb[2] + thresh])

maskYCB = cv2.inRange(brightYCB, minYCB, maxYCB)
resultYCB = cv2.bitwise_and(brightYCB, brightYCB, mask = maskYCB)

#convert 1D array to 3D, then convert it to LAB and take the first element
lab = cv2.cvtColor( np.uint8([[bgr]] ), cv2.COLOR_BGR2LAB)[0][0]

minLAB = np.array([lab[0] - thresh, lab[1] - thresh, lab[2] - thresh])
maxLAB = np.array([lab[0] + thresh, lab[1] + thresh, lab[2] + thresh])

maskLAB = cv2.inRange(brightLAB, minLAB, maxLAB)
resultLAB = cv2.bitwise_and(brightLAB, brightLAB, mask = maskLAB)

cv2.imshow("Result BGR", resultBGR)
cv2.imshow("Result HSV", resultHSV)
cv2.imshow("Result YCB", resultYCB)
cv2.imshow("Output LAB", resultLAB)

C++

//C++ code
cv::Vec3b bgrPixel(40, 158, 16);
// Create Mat object from vector since cvtColor accepts a Mat object
Mat3b bgr (bgrPixel);

//Convert pixel values to other color spaces.
Mat3b hsv,ycb,lab;
cvtColor(bgr, ycb, COLOR_BGR2YCrCb);
cvtColor(bgr, hsv, COLOR_BGR2HSV);
cvtColor(bgr, lab, COLOR_BGR2Lab);
//Get back the vector from Mat
Vec3b hsvPixel(hsv.at<Vec3b>(0,0));
Vec3b ycbPixel(ycb.at<Vec3b>(0,0));
Vec3b labPixel(lab.at<Vec3b>(0,0));

int thresh = 40;

cv::Scalar minBGR = cv::Scalar(bgrPixel.val[0] - thresh, bgrPixel.val[1] - thresh, bgrPixel.val[2] - thresh)
cv::Scalar maxBGR = cv::Scalar(bgrPixel.val[0] + thresh, bgrPixel.val[1] + thresh, bgrPixel.val[2] + thresh) 

cv::Mat maskBGR, resultBGR;
cv::inRange(bright, minBGR, maxBGR, maskBGR);
cv::bitwise_and(bright, bright, resultBGR, maskBGR);

cv::Scalar minHSV = cv::Scalar(hsvPixel.val[0] - thresh, hsvPixel.val[1] - thresh, hsvPixel.val[2] - thresh)
cv::Scalar maxHSV = cv::Scalar(hsvPixel.val[0] + thresh, hsvPixel.val[1] + thresh, hsvPixel.val[2] + thresh) 

cv::Mat maskHSV, resultHSV;
cv::inRange(brightHSV, minHSV, maxHSV, maskHSV);
cv::bitwise_and(brightHSV, brightHSV, resultHSV, maskHSV);

cv::Scalar minYCB = cv::Scalar(ycbPixel.val[0] - thresh, ycbPixel.val[1] - thresh, ycbPixel.val[2] - thresh)
cv::Scalar maxYCB = cv::Scalar(ycbPixel.val[0] + thresh, ycbPixel.val[1] + thresh, ycbPixel.val[2] + thresh) 

cv::Mat maskYCB, resultYCB;
cv::inRange(brightYCB, minYCB, maxYCB, maskYCB);
cv::bitwise_and(brightYCB, brightYCB, resultYCB, maskYCB);

cv::Scalar minLAB = cv::Scalar(labPixel.val[0] - thresh, labPixel.val[1] - thresh, labPixel.val[2] - thresh)
cv::Scalar maxLAB = cv::Scalar(labPixel.val[0] + thresh, labPixel.val[1] + thresh, labPixel.val[2] + thresh) 

cv::Mat maskLAB, resultLAB;
cv::inRange(brightLAB, minLAB, maxLAB, maskLAB);
cv::bitwise_and(brightLAB, brightLAB, resultLAB, maskLAB);

cv2::imshow("Result BGR", resultBGR)
cv2::imshow("Result HSV", resultHSV)
cv2::imshow("Result YCB", resultYCB)
cv2::imshow("Output LAB", resultLAB)

Quan sát kết quả thì RGB và LAB trong trường hợp này là đủ để phát hiện màu sắc.

Trong khi đó việc áp dụng cùng một ngưỡng cho hình ảnh chụp trong nhà không thể phát hiện các mặt màu xanh lục trong tất cả các không gian màu. Như vậy, cùng một ngưỡng như vậy nhưng không hiệu quả đối với hình ảnh tối. Thực hiện phát hiện các mặt màu vàng bằng cách sử dụng cùng một kỹ thuật và ngưỡng (đối với màu vàng) thu được từ ảnh trong điều kiện sáng. Chúng ta thấy là HSV và YCrCb không hoạt động tốt.

Thực hiện phát hiện các mặt màu vàng từ ảnh trong điều kiện tối với cùng giá trị ngưỡng thì tất cả các không gian màu đều không thành công.

Lý do là ở đây giá trị ngưỡng 40 được chọn một cách thủ công ngẫu nhiên. Do đó cần có một số cách phương pháp để tìm các giá trị ngưỡng chính xác.

Đây là ảnh chụp màn hình của bản demo thử nghiệm với các giá trị khác nhau để phát hiện màu cụ thể trong tất cả các không gian màu cho một hình ảnh nhất định. Bạn thể download soure code tại đây.

6. Phân tích dữ liệu để nâng cao chất lượng độ phân giải

Bước 1: Thu thập dữ liệu

Thực hiện thu thập 10 hình ảnh của khối lập phương trong các điều kiện ánh sáng khác nhau và cắt từng màu riêng biệt để có 6 bộ dữ liệu cho 6 màu khác nhau. Quan sát hình ảnh dưới để có thể thấy mức độ thay đổi của màu sắc bằng mắt thường.

Bước 2: Tính toán biểu đồ mật độ

Kiểm tra sự phân bố của một màu cụ thể chẳng hạn, xanh lam hoặc vàng trong các không gian màu khác nhau. Biểu đồ mật độ hoặc Biểu đồ 2D cung cấp thông tin về sự khác nhau đối với giá trị của một màu nhất định. Ví dụ: theo lý thuyết kênh màu xanh lam của hình ảnh có màu xanh lam phải luôn có giá trị là 255. Nhưng trên thực tế, nó được phân phối từ 0 đến 255. Đoạn code dưới đây là cho không gian màu BGR. Bạn có thể thực hiện tương tự cho tất cả các không gian màu.

– Trước tiên, tải tất cả hình ảnh của các mặt màu xanh lam hoặc màu vàng

#python
B = np.array([])
G = np.array([])
R = np.array([])
im = cv2.imread(fi)

– Tách các kênh, sau đó khởi tạo và sắp xếp cho từng kênh bằng cách thêm các giá trị từ mỗi hình ảnh.

#python
b = im[:,:,0]
b = b.reshape(b.shape[0]*b.shape[1])
g = im[:,:,1]
g = g.reshape(g.shape[0]*g.shape[1])
r = im[:,:,2]
r = r.reshape(r.shape[0]*r.shape[1])
B = np.append(B,b)
G = np.append(G,g)
R = np.append(R,r)

– Sử dụng biểu đồ histogram từ matplotlib để vẽ biểu đồ 2D

#python
nbins = 10
plt.hist2d(B, G, bins=nbins, norm=LogNorm())
plt.xlabel('B')
plt.ylabel('G')
plt.xlim([0,255])
plt.ylim([0,255])

Quan sát kết quả với cùng độ chiếu sáng

Biểu đồ mật độ hiển thị sự thay đổi của các giá trị trong các kênh màu cho 2 hình ảnh có cùng độ sáng của màu xanh lam
Biểu đồ mật độ hiển thị sự thay đổi của các giá trị trong các kênh màu cho 2 hình ảnh có cùng độ sáng của màu vàng

Có thể thấy rằng trong điều kiện gần như nhau về độ sáng, tất cả các biểu đồ rất đặc. Một số điểm cần lưu ý:

– YCrCb và LAB mật độ đặc hơn nhiều so với không gian khác.

– Trong HSV, có sự thay đổi theo hướng S (độ tinh khiết của màu sắc) nhưng rất ít biến đổi theo hướng H.

Quan sát kết quả với độ chiếu sáng khác nhau

Biểu đồ mật độ hiển thị sự thay đổi các giá trị trong các kênh màu trong điều kiện chiếu sáng khác nhau cho màu xanh lam
Biểu đồ mật độ hiển thị sự thay đổi các giá trị trong các kênh màu trong điều kiện chiếu sáng khác nhau cho màu vàng

Khi độ sáng thay đổi lớn, có thể thấy rằng:

– Chúng ta luôn muốn làm việc với một không gian màu với biểu đồ mật độ đặc nhất/tập trung nhất cho các kênh màu.

– Biểu đồ mật độ cho RGB tăng mạnh. Điều này có nghĩa là sự thay đổi trong giá trị của các kênh là rất cao và việc cố định một ngưỡng là một vấn đề lớn. Việc thay đổi phạm vi rộng hơn sẽ phát hiện các màu tương tự với màu mong muốn (False Positives) và phạm vi thấp hơn sẽ không phát hiện màu mong muốn trong các ánh sáng khác nhau (False Negatives).

– Trong HSV, vì chỉ có thành phần H chứa thông tin về màu tuyệt đối. Vì vậy, nó trở thành lựa chọn đầu tiên về không gian màu vì có thể điều chỉnh (H) để chỉ định màu so với 2 thành phần trong YCrCb (Cr và Cb) và LAB (A và B).

– So sánh biểu đồ của YCrCb và LAB cho thấy LAB có mật độ đặc hơn. Vì vậy, lựa chọn tốt nhất tiếp theo là không gian màu LAB.

Kết quả

Phần này sẽ trình bày kết quả để phát hiện mặt màu xanh và màu vàng bằng cách lấy các giá trị ngưỡng từ các biểu đồ mật độ và áp dụng các giá trị đó cho các không gian màu tương ứng theo cách đã giới thiệu ở các phần trên. Ở đây chúng ta không phải quan tâm về thành phần Cường độ khi đang làm việc trong không gian màu HSV, YCrCb và LAB. Chúng ta chỉ cần xác định các ngưỡng cho các thành phần màu. Các giá trị đã áp dụng để tạo ra kết quả được hiển thị trong các hình.

Ảnh demo 1
Kết quả phát hiện màu vàng trong hình ảnh demo
Kết quả phát hiện màu xanh lam trong hình ảnh demo
Ảnh demo 2
Kết quả phát hiện màu vàng trong ảnh demo 2
Kết quả phát hiện màu xanh lam trong hình ảnh demo
Ảnh demo 3
Kết quả phát hiện màu vàng trong ảnh demo 3
Kết quả phát hiện màu xanh lam trong ảnh demo 3

Trong các kết quả trên, thì giá trị được lấy trực tiếp từ biểu đồ mật độ. Chúng ta cũng có thể chọn lấy các giá trị thuộc về vùng dày đặc nhất trong biểu đồ mật độ, điều này sẽ giúp kiểm soát dải màu chặt chẽ hơn. Điều đó sẽ để lại một số điểm ảnh rải rác và nhiễu mà có thể được làm sạch bằng cách sử dụng các thao tác Erosion và Dilation, sau đó là Lọc.

Như vậy bài viết đã giới thiệu về các không gian màu trong OpenCV và áp dụng các thuộc tính của chúng phục vụ cho một số bài toán xử lý ảnh. Source code của các ví dụ trong bài viết có thể download tại đây. Ở bài viết tiếp theo chúng ta sẽ tìm hiểu về lọc ảnh sử dụng tích chập trong OpenCV.

Biên dịch: Thảo Nguyễn

Để cập nhật tin tức công nghệ mới nhất và các sản phẩm của công ty AIoT JSC, vui lòng truy cập link: http://aiots.vn hoặc linhkienaiot.com

0 0 Phiếu bầu
Article Rating
Subscribe
Notify of
guest
0 Comments
Phản hồi nội tuyến
Xem tất cả các bình luận