對於黯淡的彩色圖像調節效果.........
對於暗淡圖像的調節,灰度直方圖基本均勻碾平:
對於過度飽和圖像的效果,右上角展示了直返圖的效果,可以看見基本均勻碾平了:
參考代碼:
#include "string"
#include "vector"
#include <windows.h>
#include <opencv2/legacy/legacy.hpp>
#include <opencv2/nonfree/nonfree.hpp>//opencv_nonfree模塊:包含一些擁有專利的算法,如SIFT、SURF函數源碼。
#include "opencv2/core/core.hpp"
#include "opencv2/features2d/features2d.hpp"
#include "opencv2/highgui/highgui.hpp"
#include <opencv2/nonfree/features2d.hpp>
using namespace cv;
using namespace std;
/// 全局變量的聲明與初始化
const int alpha_slider_max = 50;//滑條最大值
unsigned char *grayimg;
unsigned char *colorimg;
int smin, smax; // 飽和率
Mat m_gImg;
Mat m_gImgSrc;
bool isGray;
#define MAX_UCHAR_PIXEL 255
void on_trackbar_gray(int, void*);
void on_trackbar_color(int, void*);
static void gray_quantiles(const unsigned char *pImgData, size_t img_size,
size_t flat_min, size_t flat_max,
unsigned char *pMinPixel, unsigned char *pMaxPixel, int nchannel);
static void gray_minmax(const unsigned char *pImgData, size_t img_size,
unsigned char *pMinPixel, unsigned char *pMaxPixel, int nchannel);
static unsigned char *gray_rescale(unsigned char *pImgData, size_t img_size,
unsigned char min, unsigned char max, int nchannel);
unsigned char *gray_balance(unsigned char *pImgData, size_t img_size,
size_t flat_min, size_t flat_max, int nchannel = 0);
void my_image_enhancement();
unsigned char *color_balance(unsigned char *pImgData, size_t size,
size_t flat_min, size_t flat_max);
bool draw_hist(Mat &img);
Mat getHistogramImage(const Mat& hist, Size imgSize);
int main(int argc, char **argv)
{
//const char* img_path = "oct.bmp";
//const char* img_path = "colors_large.png";
//const char* img_path = "input_0.png";
//const char* img_path = "a1.jpg";
//const char* img_path = "seed.tif";
const char* img_path = "CT.bmp";
m_gImgSrc = imread(img_path, CV_LOAD_IMAGE_ANYCOLOR);//CV_LOAD_IMAGE_COLOR,CV_LOAD_IMAGE_GRAYSCALE
if (!m_gImgSrc.data) { printf("Error loading src1 \n"); return -1; }
imshow("原效果圖", m_gImgSrc);
if (m_gImgSrc.channels() == 1)
isGray = true;//全局變量
my_image_enhancement();
return EXIT_SUCCESS;
}
/**
* @brief 獲得指定間隔下(flat_min,flat_max)的最大最小像素值
*
* @param pImgData, 輸入/輸出圖像數據
* @param img_size, 圖像圖像數據的大小(高度*寬度)
* @param flat_min、flat_max ,需要展平的像素數量
* @param pMinPixel、 pMaxPixel,保存限定像素值間隔下(flat_min,flat_max)的最大最小值,如果爲NULL則忽視
*/
static void gray_quantiles(const unsigned char *pImgData, size_t img_size,
size_t flat_min, size_t flat_max,
unsigned char *pMinPixel, unsigned char *pMaxPixel, int nchannel)
{
int Channels = 1;
if (!isGray)
Channels = 3;
// 直方圖必須包含所有可能的"unsigned char"值,包括0
size_t h_size = MAX_UCHAR_PIXEL + 1;
size_t histogram[MAX_UCHAR_PIXEL + 1];
size_t i;
// 計算累積直方圖
memset(histogram, 0x00, h_size * sizeof(size_t));
for (i = 0; i < img_size; i++)
histogram[(size_t)pImgData[i*Channels+nchannel]] += 1;
for (i = 1; i < h_size; i++)
histogram[i] += histogram[i - 1];//累積直方圖
// 獲得新的最大最小像素值
if (NULL != pMinPixel) {
// 正向遍歷累積直方圖
// 找到第一個大於nb_min的像素值
i = 0;
while (i < h_size && histogram[i] <= flat_min)
i++;
//當前位置的值即爲所需最小像素值
*pMinPixel = (unsigned char)i;
}
if (NULL != pMaxPixel) {
// 反向遍歷累積直方圖
// 找到第一個小於等於(size - flat_max)的像素值
i = h_size - 1;
while (i < h_size && histogram[i] >(img_size - flat_max))
i--;
// 如果i不是在直方圖末尾
// 調到臨近的下一個位置,該位置的值 > (size - nb_max)
if (i < h_size - 1)
i++;
*pMaxPixel = (unsigned char)i;
}
return;
}
/**
* @brief,獲取圖像數據的最大最小像素,在pMinPixel、pMaxPixel中
*
* @param pimgdata,輸入圖像數據
* @param img_size,圖像數組的大小
* @param pMinPixel、pMaxPixel,保留圖像數據的最大最小值,如果爲NULL則忽視
*/
static void gray_minmax(const unsigned char *pImgData, size_t img_size,
unsigned char *pMinPixel, unsigned char *pMaxPixel, int nchannel)
{
unsigned char min_pixel, max_pixel;
size_t i;
int Channels = 1;
if (!isGray)
Channels = 3;
//計算圖像數據中的最小和最大像素值
min_pixel = pImgData[0];
max_pixel = pImgData[0];
for (i = 1; i < img_size; i++) {
if (pImgData[i*Channels + nchannel] < min_pixel)
min_pixel = pImgData[i*Channels + nchannel];
if (pImgData[i*Channels + nchannel] > max_pixel)
max_pixel = pImgData[i*Channels + nchannel];
}
//將結果保存在指針中,並返回
if (NULL != pMinPixel)
*pMinPixel = min_pixel;
if (NULL != pMaxPixel)
*pMaxPixel = max_pixel;
return;
}
/**
* @brief 重新調整圖像數據
*
* 該函數原地操作,根據指定的兩個邊界(flat_min, flat_max)重新調整像素值(全部增強)
* 並且小於flat_min的原像素置0,大於flat_max的原像素置爲 MAX_UCHAR_PIXEL,即使之飽和.
*
* @param pImgData,輸入/輸出圖像數據
* @param img_size, 輸入圖像數據的尺寸
* @param flat_min, flat_max 需要被碾平(使其飽和或者衰減至最小值)的最小最大像素值
*
* @return pImgData,調整後的圖像像素值
*/
static unsigned char *gray_rescale(unsigned char *pImgData, size_t img_size,
unsigned char flat_min, unsigned char flat_max, int nchannel)
{
size_t i;
int Channels = 1;
if (!isGray)
Channels = 3;
if (flat_max < flat_min){
for (i = 0; i < img_size; i++)
pImgData[i * Channels + nchannel ] = MAX_UCHAR_PIXEL / 2;//全部取一樣的值---圖像變灰
}else {
// 建立轉換表
unsigned char norm[MAX_UCHAR_PIXEL + 1];
for (i = 0; i < flat_min; i++)//小於flat_min的置零(變黑)
norm[i] = 0;
for (i = flat_min; i < flat_max; i++)
norm[i] = (unsigned char)((i - flat_min) * MAX_UCHAR_PIXEL
/ (double)(flat_max - flat_min) + 0.5);
for (i = flat_max; i < MAX_UCHAR_PIXEL + 1; i++)//大於flat_max的置255(變白)
norm[i] = MAX_UCHAR_PIXEL;
// 根據表重新調整圖像數據
for (i = 0; i < img_size; i++)
pImgData[i * Channels + nchannel] = norm[(size_t)pImgData[i * Channels + nchannel]];
}
return pImgData;
}
/**
* @brief 圖像的顏色平衡核心函數
*/
unsigned char *gray_balance(unsigned char *pImgData, size_t img_size,
size_t flat_min, size_t flat_max, int nchannel)
{
unsigned char min, max;
// 數據的合理性檢查
if (NULL == pImgData) {//無數據
fprintf(stderr, "錯誤:該指針爲NULL!\n");
abort();
}
if (flat_min + flat_max > img_size) {
flat_min = (img_size - 1) / 2;
flat_max = (img_size - 1) / 2;
fprintf(stderr, "需要被碾平的像素數目太大\n");
fprintf(stderr, "使用(size - 1) / 2\n");
}
// 獲取圖像中的最大、最小像素值/或者指定間隔範圍的最大最小值,3
if (0 != flat_min || 0 != flat_max)
gray_quantiles(pImgData, img_size, flat_min, flat_max, &min, &max,nchannel);
else
gray_minmax(pImgData, img_size, &min, &max, nchannel);//獲得圖像數據的最小最大值
// 重新平衡像素
(void)gray_rescale(pImgData, img_size, min, max, nchannel);
return pImgData;
}
void my_image_enhancement()
{
smin = 0;
smax = 0;//初始化飽和率
if (isGray)//灰度圖
{
/// 創建窗體
namedWindow("灰度調節效果圖", 1);
/// 在創建的窗體中創建2個滑動條控件
createTrackbar("smin(0-100(此時最大50)):", "灰度調節效果圖", &smin, alpha_slider_max, on_trackbar_gray);
createTrackbar("smax(0-100(此時最大50)):", "灰度調節效果圖", &smax, alpha_slider_max, on_trackbar_gray);
/// 結果在回調函數中顯示
on_trackbar_gray(smin, 0);
on_trackbar_gray(smax, 0);
waitKey(0);
destroyWindow("灰度調節效果圖");
}else{
/// 創建窗體
namedWindow("彩色調節效果圖", 1);
/// 在創建的窗體中創建2個滑動條控件
createTrackbar("smin(0-100(此時最大50)):", "彩色調節效果圖", &smin, alpha_slider_max, on_trackbar_color);
createTrackbar("smax(0-100(此時最大50)):", "彩色調節效果圖", &smax, alpha_slider_max, on_trackbar_color);
/// 結果在回調函數中顯示
on_trackbar_color(smin, 0);
on_trackbar_color(smax, 0);
waitKey(0);
destroyWindow("彩色調節效果圖");
}
return;
}
unsigned char *color_balance(unsigned char *pImgData, size_t size,
size_t flat_min, size_t flat_max)
{
(void)gray_balance(pImgData, size, flat_min, flat_max,0);
(void)gray_balance(pImgData, size, flat_min, flat_max,1);
(void)gray_balance(pImgData, size, flat_min, flat_max,2);
return pImgData;
}
//opencv的回調函數
void on_trackbar_gray(int, void*)
{
double ss_min = (double)(smin);
double ss_max = (double)(smax);
m_gImg = m_gImgSrc.clone();//深複製(重新開闢了內存,注意區分淺複製)
grayimg = m_gImg.data;; //輸入/輸出數據
size_t img_size = m_gImg.rows * m_gImg.cols;
//執行算法
grayimg = gray_balance(grayimg, img_size,
img_size * (ss_min / 100.0),
img_size * (ss_max / 100.0));
//顯示圖像
m_gImg.data = grayimg;
draw_hist(m_gImg);
imshow("灰度調節效果圖", m_gImg);
}
//opencv的回調函數
void on_trackbar_color(int, void*)
{
double ss_min = (double)(smin);
double ss_max = (double)(smax);
m_gImg = m_gImgSrc.clone();//深複製(重新開闢了內存,注意區分淺複製)
colorimg = m_gImg.data;; //輸入/輸出數據
size_t img_size = m_gImg.rows * m_gImg.cols;
//執行算法
colorimg = color_balance(colorimg, img_size,
img_size * (ss_min / 100.0),
img_size * (ss_max / 100.0));
//顯示圖像
m_gImg.data = colorimg;
draw_hist(m_gImg);
imshow("彩色調節效果圖", m_gImg);
}
bool draw_hist(Mat &src)
{
if (src.data == NULL)
return false;
if (!isGray)
{
/// 分割成3個單通道圖像 ( R, G 和 B )
vector<Mat> rgb_planes;
split(src, rgb_planes);
/// 設定bin數目
int histSize = 255;
/// 設定取值範圍 ( R,G,B) )
float range[] = { 0, 255 };
const float* histRange = { range };
bool uniform = true;
bool accumulate = false;
Mat r_hist, g_hist, b_hist;
/// 計算直方圖:
calcHist(&rgb_planes[0], 1, 0, Mat(), r_hist, 1, &histSize, &histRange, uniform, accumulate);
calcHist(&rgb_planes[1], 1, 0, Mat(), g_hist, 1, &histSize, &histRange, uniform, accumulate);
calcHist(&rgb_planes[2], 1, 0, Mat(), b_hist, 1, &histSize, &histRange, uniform, accumulate);
// 創建直方圖畫布
int hist_w = 400; int hist_h = 400;
int bin_w = cvRound((double)hist_w / histSize);
Mat histImageR(hist_w, hist_h, CV_8UC3, Scalar(0, 0, 0));
Mat histImageG(hist_w, hist_h, CV_8UC3, Scalar(0, 0, 0));
Mat histImageB(hist_w, hist_h, CV_8UC3, Scalar(0, 0, 0));
/// 將直方圖歸一化到範圍 [ 0, histImage.rows ]
normalize(r_hist, r_hist, 0, histImageR.rows, NORM_MINMAX, -1, Mat());
normalize(g_hist, g_hist, 0, histImageG.rows, NORM_MINMAX, -1, Mat());
normalize(b_hist, b_hist, 0, histImageB.rows, NORM_MINMAX, -1, Mat());
/// 在直方圖畫布上畫出直方圖
for (int i = 1; i < histSize; i++)
{
line(histImageR, Point(bin_w*(i - 1), hist_h - cvRound(r_hist.at<float>(i - 1))),
Point(bin_w*(i), hist_h - cvRound(r_hist.at<float>(i))),
Scalar(0, 0, 255), 2, 8, 0);
line(histImageG, Point(bin_w*(i - 1), hist_h - cvRound(g_hist.at<float>(i - 1))),
Point(bin_w*(i), hist_h - cvRound(g_hist.at<float>(i))),
Scalar(0, 255, 0), 2, 8, 0);
line(histImageB, Point(bin_w*(i - 1), hist_h - cvRound(b_hist.at<float>(i - 1))),
Point(bin_w*(i), hist_h - cvRound(b_hist.at<float>(i))),
Scalar(255, 0, 0), 2, 8, 0);
}
/// 顯示直方圖
namedWindow("calcHist Demo R", CV_WINDOW_AUTOSIZE);
imshow("calcHist Demo R", histImageR);
namedWindow("calcHist Demo G", CV_WINDOW_AUTOSIZE);
imshow("calcHist Demo G", histImageG);
namedWindow("calcHist Demo B", CV_WINDOW_AUTOSIZE);
imshow("calcHist Demo B", histImageB);
//waitKey(1000);
}
else{
/// 設定bin數目
int histSize = 255;
/// 設定取值範圍 ( R,G,B) )
float range[] = { 0, 255 };
const float* histRange = { range };
bool uniform = true;
bool accumulate = false;
Mat hist;
/// 計算直方圖:
calcHist(&src, 1, 0, Mat(), hist, 1, &histSize, &histRange, uniform, accumulate);
// 創建直方圖畫布
int hist_w = 400; int hist_h = 400;
int bin_w = cvRound((double)hist_w / histSize);
Mat histImage(hist_w, hist_h, CV_8UC3, Scalar(0, 0, 0));
/// 將直方圖歸一化到範圍 [ 0, histImage.rows ]
normalize(hist, hist, 0, histImage.rows, NORM_MINMAX, -1, Mat());
/// 在直方圖畫布上畫出直方圖
for (int i = 1; i < histSize; i++)
{
line(histImage, Point(bin_w*(i - 1), hist_h - cvRound(hist.at<float>(i - 1))),
Point(bin_w*(i), hist_h - cvRound(hist.at<float>(i))),
Scalar(0, 255, 255), 1, 8, 0);
}
/// 顯示直方圖
namedWindow("calcHist gray Demo", CV_WINDOW_AUTOSIZE);
imshow("calcHist gray Demo", histImage);
//waitKey(1000);
}
return true;
}