Search…

Kỹ Thuật Grayscale và Nhị Phân Hoá Ảnh (Adaptive Threshold)

20/09/202010 min read
Giới thiệu và chi tiết các thuật toán Grayscale, ảnh nhị phân và một số thuật toán giúp biến đổi một ảnh xám thành ảnh nhị phân (Adaptive threshold).

Giới thiệu

OpenCV (Open Computer Vision) là 1 thư viện mã nguồn mở chuyên dùng để xử lý các vấn đề liên quan đến thị giác máy tính. Nhờ 1 hệ thống các giải thuật chuyên biệt, tối ưu cho việc xử lý thị giác máy tính, vì vậy tính ứng dụng của OpenCV là rất lớn. Xử lý ảnh là quá trình xử lý, thao tác hình ảnh để có 1 hình ảnh khác phù hợp với nhu cầu của người dùng, ...

Bài viết giới thiệu khái niệm và kỹ thuật ảnh xám (Grayscale) và ảnh nhị phân và nhị phân hóa (Adaptive Threshold).

GrayScale là gì?

  • Là 1 hệ thống màu có mô hình màu đơn giản nhất với 256 cấp độ xám biến thiên từ màu đen đến màu trắng.
  • Kết quả được xuất ra sẽ có màu trắng đen.
  • Được sử dụng cả trong công nghiệp in lẫn dùng trong việc thể hiện ảnh lên các thiết bị số.
  • Ảnh xám (Gray image) hay còn gọi là ảnh đơn sắc (Monochromatic), mỗi giá trị điểm ảnh (Pixel) trong ma trận điểm ảnh mang giá trị từ 0 đến 255.
  • Trong không gian màu RGB, để có 1 ảnh xám cần có phải có giá trị kênh màu Red(x, y) = Green(x, y) = Blue(x, y) (với x, y lần lượt là tọa độ của điểm ảnh).
    ss_1

Chuyển đổi hệ thống màu RGB sang Grayscale

Ảnh là tập hợp của 1 ma trận điểm ảnh (pixel), mỗi điểm ảnh có thể được biểu diễn bằng n bytes dưới các kênh màu khác nhau. Việc chuyển đổi giữa các hệ màu thông thường được thực hiện thông qua các phép biến đổi ma trận.

Bài viết sẽ giới thiệu cách chuyển đổi từ ảnh 24 bits RGB sang ảnh 8 bits Grayscale.

Công thức

Công thức tính cường độ sáng tại 1 điểm ảnh từ ảnh RGB:

I(x, y) = 0.3086 * Red(x, y) + 0.6094 * Green(x, y) + 0.0820 * Blue(x, y) 
I(x, y) = 0.299 * Red(x, y) + 0.587 * Green(x, y) + 0.114 * Blue(x, y)

Hoặc

I(x, y) = ( 2 * Red(x, y) + 5 * Green(x, y) + 1 * Blue(x, y) ) / 8 

Phân tích

  • I(x, y): cường độ sáng tại điểm ảnh (x, y) của ảnh xám.
  • Red(x, y): giá trị của kênh màu Red (Đỏ) tại điểm ảnh (x, y) của ảnh màu (RGB).
  • Green(x, y): giá trị của kênh màu Green (Xanh lá cây) tại điểm ảnh (x, y) của ảnh màu (RGB).
  • Blue(x, y): giá trị của kênh màu Blue (Xanh lơ) tại điểm ảnh (x, y) của ảnh màu (RGB).

Chú ý

  • Các phép toán trong số nguyên (Int) nhanh hơn rất nhiều trong số thực (Float).
  • Trong OpenCV, hệ thống màu có thứ tự các kênh màu là Blue-Green-Red. 
  • Các thông số dùng để tính toán cường độ sáng cho ảnh xám như: 0.3086, 0.6094, 0.0820,... được coi là những con số đẹp do người ta nghiên cứu ra. Các con số này có thể thay đổi. Có thể chọn 1 giá trị 1 kênh màu hoặc chia trung bình cộng của 3 kênh màu để tìm cường độ sáng tại 1 điểm ảnh (Pixel).

Chuyển đổi ảnh xám trong OpenCV

Phương thức cvtColor

Trong OpenCV, để chuyển 1 tấm ảnh có hệ màu RGB sang Grayscale, hay thậm chí là các không gian màu qua lại với nhau nhờ phương thức cvtColor() (Convert color). 

cv::cvtColor(cv::InputArray src, cv::OutputArray dst, int code)

Phân tích

  • src: Là hình ảnh gốc (Trong bài viết này là ảnh màu).
  • dst: Là ảnh thu được (Trong bài viết này là ảnh xám).
  • code: Là mã chuyển màu. Ví dụ: code = CV_BGR2GRAY là chuyển đổi ảnh màu thành ảnh xám,...

Code minh hoạ phương thức cvtColor trong OpenCV

// www.stdio.vn
// www.stdio.vn/users/index/11/truong-dat
#include <stdio.h>
#include "opencv2/core/core.hpp"
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"

using namespace cv;

int main()
{		
	// Read image
	Mat image = imread("stdio.png", CV_LOAD_IMAGE_COLOR);
	Mat imageGrayscale;
	// Check for valid
	if (!image.data)
	{
		printf("Could not open or find the image\n");
		return -1;
	}

	cvtColor(image, imageGrayscale, CV_BGR2GRAY);
	// Create and show image in window	
	imshow("STDIO OpenCV Sample", imageGrayscale);

	// Wait input and exit
	waitKey(0);

	return 0;
}
ss_2

Ảnh nhị phân

  • Là ảnh mà giá trị của các điểm ảnh chỉ được biểu diễn bằng hai giá trị là 0 (Đen) và 255 (Trắng) (Tương ứng với 01, nhưng để nguyên giá trị 0255 để có thể hiểu hơn trong việc tính toán).
  • Vì giá trị của điểm ảnh được biểu diễn bởi 2 giá trị là 0 hoặc 1, nên 1 điểm ảnh được biểu diễn bằng 1 bit nên ảnh có kích thước rất nhỏ.

Nhị phân hóa

Là quá trình biến đổi 1 ảnh xám thành ảnh nhị phân.

  • Gọi giá trị cường độ sáng tại 1 điểm ảnh là I(x,y) .
  • INP(x,y) là cường độ sáng của điểm ảnh trên ảnh nhị phân .
  • (Với 0 < x < image.width) và (0 < y < image.height).

Để biến đổi ảnh xám thành ảnh nhị  phân. So sánh giá trị cường độ sáng của điểm ảnh với 1 ngưỡng nhị phân T

  • Nếu I(x,y) > T thì INP(x, y) = 0 (0).
  • Nếu I(x,y) > T thì INP(x, y) = 255 (1).

Chú ý

  • Có thể chọn giá trị T từ 0 đến 255, nhưng thông thường nhiều người hay chọn 1 giá trị đó là 128 tức là giá trị trung bình của max(255) và min(0) của cường độ sáng (Intensity) của điểm ảnh.
  • Dễ dàng nhận thấy với mỗi T thì có 1 ảnh nhị phân khác nhau (Khác nhau ở đây là cường độ sáng của các tấm ảnh nhị phân với mỗi giá trị T).

Có 1 kỹ thuật gọi là nhị phân hóa ngưỡng động giúp thu được ảnh nhị phân mà không quan tâm tới cường độ sáng.

Nhị phân hóa trong OpenCV

Phương thức threshold

Để chuyển 1 ảnh thành 1 ảnh nhị phân, sử dụng phương thức threshold().

threshold(cv::InputArray src, cv::OutputArray dst, double thresh, double maxval, int type);

Phân tích

  • src: hình ảnh gốc (Trong bài viết này là ảnh màu).
  • dst: ảnh thu được (Trong bài viết này là ảnh nhị phân).
  • thresh: ngưỡng nhị phân T.
  • maxval: giá trị lớn nhất trong ảnh (maxval = 255 đối với ảnh xám).
  • type: kiểu nhị phân. 
Code minh hoạ phương thức threshold trong OpenCV
// www.stdio.vn
// www.stdio.vn/users/index/11/truong-dat
#include <stdio.h>
#include "opencv2/core/core.hpp"
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"

using namespace cv;

int main()
{		
	// Read image
	Mat image = imread("stdio.png", CV_LOAD_IMAGE_GRAYSCALE);
	Mat imageBinary;
	// Check for valid
	if (!image.data)
	{
		printf("Could not open or find the image\n");
		return -1;
	}

	threshold(image, imageBinary, 220, 255, CV_THRESH_BINARY);
	// Create and show image in window	
	imshow("STDIO OpenCV Sample", imageBinary);

	// Wait input and exit
	waitKey(0);

	return 0;
}
ss_3

Nhị phân hóa ngưỡng động

Ý tưởng:

  1. Chia ảnh thành nhiều khu vực, cửa sổ khác nhau (Region).
  2. Dùng 1 thuật toán để tìm 1 giá trị T phù hợp với từng khu vực, cửa sổ (Region).
  3. Áp dụng phương pháp nhị phân hóa cho từng khu vực, cửa sổ (Region) với T phù hợp.

Điều quan trọng trong kỹ thuật này là phải tìm 1 giá trị T phù hợp với từng khu vực, cửa sổ (Region) hoặc cả tấm ảnh. Có rất nhiều phương pháp để tìm T, ở nội dung tiếp theo sẽ giới thiệu 1 số thuật toán giúp tìm kiếm giá trị T này.

Thuật toán Otsu

Bước 1: Xác định T1. Giá trị cho T1 ban đầu nên chọn là (0+255) / 2 = 128.

Bước 2: Phân loại thành 2 nhóm điểm ảnh.  

  • Loại 1 (Type1): chứa tất cả các điểm ảnh có giá trị cường độ sáng (Intensity) <= T.    
  • Loại 2 (Type2): chứa tất cả các điểm ảnh có giá trị cường độ sáng (Intensity) > T.

Bước 3: Tính giá trị cường độ sáng trung bình (iAverage) cho Type1 (iAverage1) và Type2 (iAverage2).

Bước 4: Tính giá trị T2 theo công thức (iAverage1 + iAverage2) /2.

Bước 5: So sánh T1T2

  • Nếu giá trị chênh lệch của T1T2 <= Delta (1 giá trị cho trước) thì T2 chính là T cần tìm. 
  • Nếu giá trị chênh lệch của T1T2 > Delta thì quay lại Bước 1.
ss_4

Thuật toán đối xứng

Bước 1: khởi tạo mảng Histogram(histogram). Tìm giá trị cường độ sáng (intensityMax) có tuần suất xuất hiện nhiều nhất histogram[intensityMax].

Bước 2: duyệt toàn bộ các mức xám giảm từ 255 đến intensityMax. Nếu tại mức xám nào có tuần suất xuất hiện trên ảnh là 5% thì dừng lại. Lấy giá trị đối xứng qua histogram[intensityMax] là ngưỡng động T.

for (int indexIntensity = 255; indexIntensity >= intensityMax; indexIntensity--) 
{  
    frequency = histogram[indexIntensity];

    if  ((float)frequency / (image.Width * image.Height) == 0.05f) {
        split = indexInteensity;
        break;
    }
} 
    
T = intensityMax - (split - intensityMax);
ss_5

Thuật toán tam giác

Bước 1: Khởi tạo mảng Histogram(histogram).

  • Tìm giá trị intensityMax và histogram[intensityMax]
  • Tìm giá trị intensityMin và histogram[intensittyMin].    

Bước 2: Duyệt toàn bộ các mức xám từ intensityMin đến intensityMax. Tính khoảng cách tương ứng sau đó xét ngưỡng T bằng giá trị mức xám có khoảng cách lớn nhất. 

for(int index = intensityMin+1; index < intensityMax; index++)
{ 
    distance = findDistance (Point(intensityMax, histogram[intensityMax]),
                             Point(intensityMin, histogram[intensityMin]),
                             Point(index, histogram[index]));
  
    if (distanceMax < distance) {
        distanceMax = distance;
        T = index;   
    }  
}

Phân tích

Phương thức findDistance(Point a, Point b, Point c) là tìm khoảng cách từ 1 điểm đến 1 đường thẳng với các tham số:

  • ab: lần lượt là 2 điểm khác biệt trên đường thẳng.
  • c: là điểm cần tìm khoảng cách tới đường thẳng.
ss_6

Nhị phân hóa ngưỡng động trong OpenCV

Phương thức adaptiveThreshold

Để chuyển 1 ảnh thành 1 ảnh nhị phân, sử dụng phương thức adaptiveThreshold()

adaptiveThreshold(cv::InputArray src, cv::OutputArray dst, double maxValue,
                  int adaptiveMethod, int thresholdType, int blockSize, double C);

Phân tích

  • src: hình ảnh gốc (Trong bài viết này là ảnh màu).
  • dst: ảnh thu được (Trong bài viết này là ảnh nhị phân).
  • thresh: ngưỡng nhị phân T.
  • maxValue: giá trị lớn nhất trong ảnh (maxval = 255 đối với ảnh xám).
  • adaptiveMethod: cách thức nhị phân với ngưỡng động, nó chính là cách tính giá trị ngưỡng nhị phân trong từng vùng cần nhị phân.
  • thresholdType: kiểu nhị phân. 
  • blockSize: kích thước của cửa sổ (Region) áp dụng cho việc tính toán ngưỡng động (nên chọn các giá trị %3 = 0 || %5 = 0 || %7 = 0).
  • C: thông số để bù trừ trong trường hợp ảnh có độ tương phản quá lớn.
Code minh hoạ phương thức adaptiveThreshold trong OpenCV
#include <stdio.h>
#include "opencv2/core/core.hpp"
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"

using namespace cv;

int main()
{		
	// Read image
	Mat image = imread("stdio.png", CV_LOAD_IMAGE_GRAYSCALE);
	Mat imageBinary;
	// Check for valid
	if (!image.data)
	{
		printf("Could not open or find the image\n");
		return -1;
	}

	adaptiveThreshold(image, imageBinary, 255, CV_ADAPTIVE_THRESH_MEAN_C, 
                                        CV_THRESH_BINARY, 9, 0);
	// Create and show image in window	
	imshow("STDIO OpenCV Sample", imageBinary);

	// Wait input and exit
	waitKey(0);

	return 0;
}
ss_7
IO Stream

IO Stream Co., Ltd

30 Trinh Dinh Thao, Hoa Thanh ward, Tan Phu district, Ho Chi Minh city, Vietnam
+84 28 22 00 11 12
developer@iostream.co

383/1 Quang Trung, ward 10, Go Vap district, Ho Chi Minh city
Business license number: 0311563559 issued by the Department of Planning and Investment of Ho Chi Minh City on February 23, 2012

©IO Stream, 2013 - 2024