數字圖像處理,一種簡單的顏色平衡算法


對於黯淡的彩色圖像調節效果.........



對於暗淡圖像的調節,灰度直方圖基本均勻碾平




對於過度飽和圖像的效果,右上角展示了直返圖的效果,可以看見基本均勻碾平了:



參考代碼:

#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;
}


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章