Nẳm trong loạt bài viết trong chương trình Tự Học OpenCV Qua Các Ví Dụ Thực Tế. Bài viết sẽ giới thiệu tăng giảm độ sáng, độ tương phản của một tấm ảnh. Giới thiệu, cách vẽ và tác dụng biểu đồ tần số Histogram trong OpenCV
Khoa học máy tính Trương Xuân Đạt 2015-09-02 05:33:00

Giới thiệu

OpenCV (Open Computer Vision) là một 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ờ một 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ó một hình ảnh khác phù hợp với nhu cầu của người dùng,...

Tiền đề bài viết

Bài viết nằm trong loạt bài viết chương trình Tự Học OpenCV Qua Các Ví Dụ Thực Tế được tài trợ bởi STDIO. Trong bài biết này sẽ hướng dẫn:

  • Thay đổi độ sáng, tương phản của một hình ảnh.
  • Tìm hiểu biều đồ tần số Histogram.

Đối tượng hướng đến

Bài viết hướng tới các bạn đọc mới tìm hiểu về xử lý ảnh, đặc biệt là với OpenCV và chưa có kiến thức tương đương.

Tăng giảm độ sáng, độ tương phản cho một tấm ảnh trong OpenCV

Một hình ảnh được lưu trữ là một  ma trận các điểm ảnh (Pixel). Trong OpenCV nó được biểu diễn dưới dạng cv::Mat. Tôi có một đoạn code sau:

cv::Mat Example(2, 3, CV_8UC3, Scalar(1, 2, 3));
Phân tích

Đây là một cách khởi tạo đơn giản với kiểu dữ liệu cv::Mat. Bản chất của cv::Mat là một ma trận.Ví dụ này giống như cấu trúc một ảnh có không gian màu RGB. 

  • Hai tham số đầu tiên là kích thước của ma trận lần lượt là hàng (Rows) = 2 và cột (Colums) = 3 của Example.
  • CV_8UC3: Với mỗi điểm trên cv::Mat Example được lưu trữ dưới dạng: Loại usigned char (0-255) có 8 bit và có 3 kênh.
CV_[Số bit cho mỗi kênh của điểm][Signed hoặc Unsigned][Kiểu dữ liệu]C[Số lượng kênh]
  • Scalar(1, 2, 3): Với mỗi điểm trên Mat Example được khởi tạo các giá trị 1, 2 và 3 tương ứng với các kênh 1, kênh 2 và kênh 3. 

ss_1

Để có thể truy cập giá trị điểm ảnh(Pixel) với những kênh màu, bạn sử dụng mẫu sau:

image.at<cv::Vec3b>(row, col)[channel]
  • row: Là hàng thứ row trong image.
  • col: Là cột thứ col trong image.
  • channel: Là kênh màu của image. Ví dụ: với không gian màu R-G-B, channel có giá trị lần lượt là channel = 1, channel = 2=, channel = 3. Trong OpenCV kênh màu của không gian R-G-B được xếp theo thứ tự là B-G-R.

Để thay đổi độ sáng, độ tương phản của một tấm ảnh, bạn sử dụng công thức sau:

G(x,y) = A*F(x, y) + B
Phân tích
  • F(x, y): là giá trị pixel trong ảnh chưa được xử lý ở vị trí (x, y).
  • G(x, y): là giá trị pixel trong ảnh đã được xử lý ở vị trí (x, y).
  • A: Là giá trị đặc trưng cho chênh lệch độ tương phản của G(x, y) và F(x, y). Hay nói một cách khác chính là: Ảnh G tương phản gấp A lần với ảnh F.
  • B: Với B#0, ảnh G có độ sáng thay đổi một lượng là B.

Ví dụ

// 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 imageDst = image.clone(); // sao chép 2 Mat
	// Check for valid
	if (!image.data)
	{
		printf("Could not open or find the image\n");
		return -1;
	}

#define NUMBER_CHANNEL_RGB_COLOR_MODEL 3
    double contrast = 2.0; // A
    int brightness = 30; // B
    for (int indexRow = 0; indexRow < image.rows; indexRow++) {
        for (int indexCol = 0; indexCol < image.cols; indexCol++) {
            for (int channel = 0; channel < NUMBER_CHANNEL_RGB_COLOR_MODEL; channel++) { 
                imageDst.at<Vec3b>(indexRow, indexCol)[channel] = saturate_cast<uchar> 
                       (contrast*(imageDst.at<Vec3b>(indexRow, indexCol)[channel]) + brightness);
            }
	    }
    }
    
    imshow("Before", image);
	imshow("After", imageDst);
 
	// Wait input and exit
	waitKey(0);
 
	return 0;
}
Phân tích
  • Phương thức saturete_cast() giúp tránh tình trạng tràn số hay kiểu dữ liệu không tương thích. Vì giá trị điểm ảnh (Pixel) phải nằm trong khoảng 0-255 (unsigned char), trong khi theo công thức G(x,y) = A*F(x, y) + B thì giá trị có thể là G(x, y) < 0 hoặc G(x,y) > 255.
uchar a = saturate_cast<uchar>(-100); // a = 0 (UCHAR_MIN)
short b = saturate_cast<short>(33333.33333); // b = 32767 (SHRT_MAX)

ss_2

Biểu đồ phân bố tần số Histogram

Khái niệm

  • Biểu đồ phân bố tần số (Biểu đồ phân bố mật độ, biểu đồ cột) dùng để đo tần số xuất hiện của một vấn đề nào đó, cho ta thấy rõ hình ảnh sự thay đổi, biến động của một tập dữ liệu. Đây là một khái niệm rất phổ biến, được sử dụng rộng rãi từ lĩnh vực kỹ thuật tới kinh tế. 
  • Trong xử lý ảnh, biểu đồ Histogram của một ảnh là biểu đồ mô tả sự phân bố của các giá trị mức xám của các điểm ảnh (Pixel) trong một bức ảnh hoặc một vùng ảnh(Region). 
  • Trong một ảnh, giá trị của một điểm ảnh (Pixel) thường là từ 0-255.

ss_3


Phân tích
  • Trục tung (Oy) biểu diễn số lượng điểm ảnh (Pixel) của mức xám.
  • Trục hoành (Ox) biểu diễn mức xám.
  • Giá trị lớn nhất của trục hoành chính là số lượng điểm ảnh (Pixel) có trong một bức ảnh.
  • Với ảnh màu như RGB thì có tới 3 biểu đồ Histogram thể hiện từng kênh màu.
  • Một biều đồ tốt à biểu đồ có số lượng điểm ảnh nhiều nhất ở vùng giữa (Độ sáng trung bình) và ít dần ra 2 vùng sáng tối (Ngọn núi).
// www.stdio.vn
#define MAX_INTENSITY 255

int histogram[] = new int[MAX_INTENSITY];
for (int x = 0; x < image.Width-1; x++) {
    for (int y = 0; y < image.Height-1; y++) {
        histogram[image.GetPixel[x, y].R] += 1;
    }
} // done

Tác dụng

  • Dựa vào biều đồ Histogram mà bạn có thể biết được hình ảnh sáng tối như thế nào.
  • Áp dụng cho các xử lý ảnh cao cấp khác.

ss_4

Phân tích
  • Ảnh tối là ảnh có tập trung quá nhiều điểm ảnh bên vùng tối.
  • Ảnh sáng là ảnh có tập trung quá nhiều điểm ảnh bên vùng sáng.
  • Ảnh có độ tương phản cao là ảnh có điểm ảnh tập trung nhiều ở 2 vùng sáng và tối và tập trung ít ở vùng giữa.
  • Ảnh có độ tương phản thấp là ảnh có điểm ảnh tập trung nhiều ở vùng giữa và tập trung rất ít ở vùng hai vùng sáng và tối.

Histogram trong OpenCV

Một số phương thức cơ bản

  • calcHist(): tính toán hình dạng của biểu đồ Histogram.
  • equalizeHist(): Bình thường hóa hình ảnh và độ tương phản.
  • compareHist(): So sánh hai biểu đồ Histograms.

Tính toán và vẽ biểu đồ histogram của một tấm ảnh

Doạn code dưới đây sẽ tính toán và vẽ biểu đồ Histogram của một tấm ảnh.

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

using namespace cv;
using namespace std;

int main()
{
    Mat imageSrc = imread("carStdio.png", CV_LOAD_IMAGE_GRAYSCALE);
	Mat imageGrayscale;
	int width = 400, height = 400;
	int sizeHistogram = 255;
	float range[] = { 0, 255 };
	const float* histogramRange = { range };

	calcHist(&imageSrc, 1, 0, Mat(), imageGrayscale, 1, &sizeHistogram, &histogramRange, true, false);

	int bin = cvRound((double)width / sizeHistogram);

	Mat dispHistogram(width, height, CV_8UC3, Scalar(255, 255, 255));

	normalize(imageGrayscale, imageGrayscale, 0, dispHistogram.rows, NORM_MINMAX, -1, Mat());

	for (int i = 0; i < 255; i++) {
		line(dispHistogram, Point(bin*(i), height), Point(bin*(i), height - cvRound(imageGrayscale.at<float>(i))), Scalar(0, 0, 0), 2, 8, 0);
	}

    imshoe("Car Stdio.vn", imageSrc);
	imshow("Hitogram", dispHistogram);

	// Wait input and exit
	waitKey(0);

	return 0;
}
Build/Run

ss_5

Với ảnh màu có không gian màu R-G-B, để vẽ biểu đồ Histogram cho 3 kênh màu của hình ảnh. Bạn cần phải tách ảnh thành 3 kênh màu riêng biệt.

int main()
{
	Mat imageSrc = imread("carStdio.png", CV_LOAD_IMAGE_COLOR);
	vector<Mat> imageRGB;
	// Khởi tạo các biến lưu trữ 3 bênh màu
	Mat imageRed, imageGreen, imageBlue;

	int width = 250, height = 250;
	int sizeHistogram = 255;
	float range[] = { 0, 255 };
	const float* histogramRange = { range };

	// Hàm này có tác dụng tách imageSrc thành 3 kênh màu.
	split(imageSrc, imageRGB);

	// Tính toán cho từng kênh màu và vẽ biểu đồ Histogram
	calcHist(&imageRGB[0], 1, 0, Mat(), imageRed, 1, &sizeHistogram, &histogramRange, true, false);
	calcHist(&imageRGB[1], 1, 0, Mat(), imageGreen, 1, &sizeHistogram, &histogramRange, true, false);
	calcHist(&imageRGB[2], 1, 0, Mat(), imageBlue, 1, &sizeHistogram, &histogramRange, true, false);

	int bin = cvRound((double)width / sizeHistogram);

	Mat dispRed(width, height, CV_8UC3, Scalar(255, 255, 255));
	Mat dispGreen = dispRed.clone();
	Mat dispBlue = dispRed.clone();

	normalize(imageBlue, imageBlue, 0, dispBlue.rows, NORM_MINMAX, -1, Mat());
	normalize(imageGreen, imageGreen, 0, dispGreen.rows, NORM_MINMAX, -1, Mat());
	normalize(imageRed, imageRed, 0, dispRed.rows, NORM_MINMAX, -1, Mat());

	for (int i = 0; i < 255; i++) {
		line(dispRed, Point(bin*(i), height), Point(bin*(i), height - cvRound(imageRed.at<float>(i))), Scalar(0, 0, 255), 2, 8, 0);
		line(dispGreen, Point(bin*(i), height), Point(bin*(i), height - cvRound(imageGreen.at<float>(i))), Scalar(0, 255, 0), 2, 8, 0);
		line(dispBlue, Point(bin*(i), height), Point(bin*(i), height - cvRound(imageBlue.at<float>(i))), Scalar(255, 0, 0), 2, 8, 0);
	}

	namedWindow("src", 0);
	imshow("STDIO OpenCV Sample", imageSrc);
	imshow("H blue", dispBlue);
	imshow("H green", dispGreen);
	imshow("H red", dispRed);

	// Wait input and exit
	waitKey(0);

	return 0;
}
Buid/Run

ss_6

Cân bằng Histograms

Cân bằng Histogram (Histogram equalization) là phương pháp làm cho biểu đồ Histogram của ảnh được phân bố một cách đồng đều. Đây là một cách giúp nâng cao chất lượng hình ảnh. Để cân bằng ta dùng phương thức equalizeHist().

Cân bằng histogram cho ảnh xám
Mat imageSrc = imread("carStdio.png", CV_LOAD_IMAGE_SCALE);
Mat imageDst;

equalizeHist(imageSrc, Dst);

imshow("imageSrc", src);
imshow("imageDst", dst);

ss_7

Cân bằng histogram cho ảnh màu

Để cân bằng Histogram cho một tấm ảnh màu, trước hết cần phải chuyển ảnh màu ở dạng RGB sang HSV, sau đó cân bằng thành phần kênh màu V(Value mức độ sáng) và sau đó chuyển đổi ngược lại.

Mat imageSrc = imread("carStdio.png", CV_LOAD_IMAGE_COLOR);
Mat imageHsv, imageDst;

cvtColor(imageSrc, imageHsv, CV_BGR2HSV);

vector<Mat> hsvChannels;
// Tách imageHsv thành 3 kênh màu
split(imageHsv, hsvChannels);

// Cân bằng histogram kênh màu v (Value)
equalizeHist(hsvChannels[2], hsvChannels[2]);

// Trộn ảnh
merge(hsvChannels, imageHsv);

// Chuyển đổi HSV sang RGB để hiển thị
cvtColor(imageHsv, imageDst, CV_HSV2BGR);

imshow("imageSrc", imageSrc);

imshow("imageDst", imageDst);

waitKey(0);

ss_8

Tham khảo

http://docs.opencv.org/doc/tutorials/tutorials.html