Hầu hết mọi người đều đã từng nhìn thấy quang đồ (histogram) của ảnh. Đây là dạng biểu đồ 2 chiều thể hiện độ sáng tối của ảnh 1 cách trực quan. Căn cứ vào quang đồ có thể biết ảnh thiếu sáng hay thừa sáng để cân bằng.
Quang đồ xuất hiện phổ biến trong máy ảnh, các phần mềm chỉnh sửa ảnh như Photoshop.
Cách vẽ Histogram
Histogram là biểu đồ 2 chiều với trục X là các giá trị màu của ảnh, trục Y là tổng số pixel. Với ảnh có 8 bit màu mỗi kênh thì trục X có 256 cột giá trị từ 0 – 255.
Lấy 2 ảnh làm thí dụ, 1 ảnh thiếu sáng và 1 ảnh đủ sáng
Trong ảnh thiếu sáng biểu đồ hơi lệch về bên trái, còn ảnh đủ sáng thì biểu đồ tương đối cân bằng.
Bên dưới là ảnh có đèn sáng, tức là có 1 vùng sáng hơn hẳn các vùng còn lại. Nhìn vào histogram ta thấy được 1 cột bên phải cao bất thường, đó chính đèn xe.
Cân bằng sáng (Equalize hist)
Cân bằng sáng là 1 kỹ thuật biến đổi ảnh trở về độ sáng trung bình. Ta thấy được sự khác biệt rõ ràng giữa màu sắc với histogram so với ảnh gốc.
Tuy nhiên khi sử dụng phải cẩn thận vì có thể làm mất đặc trưng của ảnh. Sau khi dùng hàm cv::equalizeHist() thì ảnh đã mất đi độ tương phản so với ban đầu.
Source code vẽ histogram
Code này được viết bằng opencv 3.x
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 |
cv::Mat DrawHistogram(cv::Mat matInput, bool drawOnInputMat) { if (!matInput.data) { std::cout<<"image input error"; return cv::Mat(); } std::vector<cv::Mat> bgr_planes; cv::split(matInput, bgr_planes); /// Establish the number of bins int histSize = 256; /// Set the ranges ( for B,G,R) ) float range[] = { 0, 256 }; const float* histRange = { range }; bool uniform = true; bool accumulate = false; cv::Mat b_hist, g_hist, r_hist; /// Compute the histograms: cv::calcHist(&bgr_planes[0], 1, 0, cv::Mat(), b_hist, 1, &histSize, &histRange, uniform, accumulate); if (matInput.channels() == 3) { cv::calcHist(&bgr_planes[1], 1, 0, cv::Mat(), g_hist, 1, &histSize, &histRange, uniform, accumulate); cv::calcHist(&bgr_planes[2], 1, 0, cv::Mat(), r_hist, 1, &histSize, &histRange, uniform, accumulate); } // Draw the histograms for B, G and R int hist_w = matInput.cols; int hist_h = matInput.rows; double bin_w = (double)hist_w / histSize; cv::Mat histImage(hist_h, hist_w, CV_8UC3, cv::Scalar(0, 0, 0)); if (drawOnInputMat) { histImage = matInput.clone(); if (histImage.channels() == 1) { cv::cvtColor(histImage, histImage, CV_GRAY2BGR); } } /// Normalize the result to [ 0, histImage.rows ] cv::normalize(b_hist, b_hist, 0, histImage.rows, cv::NORM_MINMAX, -1, cv::Mat()); cv::normalize(g_hist, g_hist, 0, histImage.rows, cv::NORM_MINMAX, -1, cv::Mat()); cv::normalize(r_hist, r_hist, 0, histImage.rows, cv::NORM_MINMAX, -1, cv::Mat()); int thickness = 1; /// Draw for each channel for (int i = 1; i < histSize; i++) { cv::line(histImage, cv::Point(bin_w*(i - 1), hist_h - cvRound(b_hist.at<float>(i - 1))), cv::Point(bin_w*(i), hist_h - cvRound(b_hist.at<float>(i))), matInput.channels() == 3 ? cv::Scalar(255, 0, 0) : cv::Scalar(255, 255, 255), thickness, 8, 0); if (matInput.channels() == 3) { cv::line(histImage, cv::Point(bin_w*(i - 1), hist_h - cvRound(g_hist.at<float>(i - 1))), cv::Point(bin_w*(i), hist_h - cvRound(g_hist.at<float>(i))), cv::Scalar(0, 255, 0), thickness, 8, 0); cv::line(histImage, cv::Point(bin_w*(i - 1), hist_h - cvRound(r_hist.at<float>(i - 1))), cv::Point(bin_w*(i), hist_h - cvRound(r_hist.at<float>(i))), cv::Scalar(0, 0, 255), thickness, 8, 0); } } return histImage; } |