关于图像降色彩后,彩色直方图统计与实际像素值不匹配问题

13 篇文章 0 订阅
11 篇文章 0 订阅

一、先来看一个通过图像遍历进行图像降彩色(Color Reduce)的例子:


遍历图像的最基本方式:at<typename>(i,j)

Mat类提供了一个at的方法用于取得图像上的点,它是一个模板函数,可以取到任何类型的图像上的点。下面我们通过一个图像处理中的实际来说明它的用法。

在实际应用中,我们很多时候需要对图像降色彩,因为256*256*256实在太多了,在图像颜色聚类或彩色直方图时,我们需要用一些代表性的颜色代替丰富的色彩空间,我们的思路是将每个通道的256种颜色用64种代替,即将原来256种颜色划分64个颜色段,每个颜色段取中间的颜色值作为代表色。

void colorReduce(Mat& image, int div)
{
	//split(image, channelsRGB);

	for (int i = 0; i<image.rows; i++)
	{
		for (int j = 0; j<image.cols; j++)
		{
			image.at<Vec3b>(i, j)[0] = image.at<Vec3b>(i, j)[0] / div*div + div / 2;
			image.at<Vec3b>(i, j)[1] = image.at<Vec3b>(i, j)[1] / div*div + div / 2;
			image.at<Vec3b>(i, j)[2] = image.at<Vec3b>(i, j)[2] / div*div + div / 2;
		}
	}
	
}

int main()
{
	Mat image = imread("123.jpg");

	imshow("input",image);

	colorReduce(image, 64);
	waitKey(0);
	imshow("output", image);

	cout << image << endl;

	waitKey(0);

	return 0;
}

可见图像经过降色彩(Color Reduce)后,像素值只有“32,96,160,224”共四个值。


二、下面再看一个图像彩色直方图统计的例子:

HistogramND.hpp
#include<opencv2\opencv.hpp>
#include<iostream>
using namespace std;
using namespace cv;

class HistogramND{
private:
	Mat image;//源图像
	int hisSize[1], hisWidth, hisHeight;//直方图的大小,宽度和高度
	float range[2];//直方图取值范围

	const float *ranges;

	//Mat channelsRGB[3];//分离的BGR通道

	vector<Mat> channelsRGB;

	MatND outputRGB[3];//输出直方图分量
public:
	HistogramND(){
		hisSize[0] = 256;
		hisWidth = 400;
		hisHeight = 400;
		range[0] = 0.0;
		range[1] = 255.0;

		ranges = &range[0];
	}

	//导入图片
	bool importImage(String path){
		image = imread(path);
	//bool importImage(Mat path){
		image = imread(path);
	//	path.copyTo(image);
		if (!image.data)
			return false;
		return true;
	}

	//分离通道
	void splitChannels(){
		split(image, channelsRGB);
		//cout << channelsRGB.at(0) << endl;
	}

	//计算直方图
	void getHistogram(){
		//calcHist(&channelsRGB[0], 1, 0, Mat(), outputRGB[0], 1, hisSize, &ranges);
		//calcHist(&channelsRGB[1], 1, 0, Mat(), outputRGB[1], 1, hisSize, &ranges);
		//calcHist(&channelsRGB[2], 1, 0, Mat(), outputRGB[2], 1, hisSize, &ranges);

		calcHist(&channelsRGB.at(0), 1, 0, Mat(), outputRGB[0], 1, hisSize, &ranges);
		calcHist(&channelsRGB.at(1), 1, 0, Mat(), outputRGB[1], 1, hisSize, &ranges);
		calcHist(&channelsRGB.at(2), 1, 0, Mat(), outputRGB[2], 1, hisSize, &ranges);


		//cout << channelsRGB[0] << endl;
		//输出各个bin的值
		for (int i = 0; i < hisSize[0]; ++i){
			cout << i << "   B:" << outputRGB[0].at<float>(i);
			cout << "   G:" << outputRGB[1].at<float>(i);
			cout << "   R:" << outputRGB[2].at<float>(i) << endl;
		}
	}

	//显示直方图
	void displayHisttogram(){
		Mat rgbHist[3];
		for (int i = 0; i < 3; i++)
		{
			rgbHist[i] = Mat(hisWidth, hisHeight, CV_8UC3, Scalar::all(0));
		}
		normalize(outputRGB[0], outputRGB[0], 0, hisWidth - 20, NORM_MINMAX);
		normalize(outputRGB[1], outputRGB[1], 0, hisWidth - 20, NORM_MINMAX);
		normalize(outputRGB[2], outputRGB[2], 0, hisWidth - 20, NORM_MINMAX);
		for (int i = 0; i < hisSize[0]; i++)
		{
			int val = saturate_cast<int>(outputRGB[0].at<float>(i));
			rectangle(rgbHist[0], Point(i * 2 + 10, rgbHist[0].rows), Point((i + 1) * 2 + 10, rgbHist[0].rows - val), Scalar(0, 0, 255), 1, 8);
			val = saturate_cast<int>(outputRGB[1].at<float>(i));
			rectangle(rgbHist[1], Point(i * 2 + 10, rgbHist[1].rows), Point((i + 1) * 2 + 10, rgbHist[1].rows - val), Scalar(0, 255, 0), 1, 8);
			val = saturate_cast<int>(outputRGB[2].at<float>(i));
			rectangle(rgbHist[2], Point(i * 2 + 10, rgbHist[2].rows), Point((i + 1) * 2 + 10, rgbHist[2].rows - val), Scalar(255, 0, 0), 1, 8);
		}

		cv::imshow("R", rgbHist[0]);
		imshow("G", rgbHist[1]);
		imshow("B", rgbHist[2]);
		imshow("image", image);
	}
};


// int main(){
	// string path = "1.jpg";
	// HistogramND hist;
	// if (!hist.importImage(path)){
		// cout << "Import Error!" << endl;
		// return -1;
	// }
	// hist.splitChannels();
	// hist.getHistogram();
	// hist.displayHisttogram();
	// waitKey(0);
	// return 0;
// }

//void run(Mat path){
void run(String path){
	HistogramND hist;
	if (!hist.importImage(path)){
		cout << "Import Error!" << endl;
		//return -1;
	}
	hist.splitChannels();
	hist.getHistogram();

	hist.displayHisttogram();
	waitKey(0);
}


该程序通过split分离BRG三通道,再用calcHist来对三通道直方图进行统计。将此头文件include到刚才的cpp文件中,并对Main函数进行相应修改:
int main()
{
	Mat image = imread("123.jpg");
	imshow("input",image);
	colorReduce(image, 64);

	waitKey(0);

	imshow("output", image);
	imwrite("output.jpg",image);

	run("output.jpg");
	
	waitKey(0);
	return 0;
}


可见BGR三通道像素值并不是如刚才进行图像降色彩(Color Reduce)后所得的结果,在非“32,96,160,224”也不为0。

三、分析问题:

(1)设想1:统计直方图程序错误
HistogramND.hpp run函数中统计直方图部分和显示直方图
hist.getHistogram();

hist.displayHisttogram();
屏蔽掉之后,再打印B通道像素值发现其 在非“ 32,96,160,224 ”也不为0。
于是怀疑是图像色彩三通道分离split的问题。
(2)设想2:色彩三通道分离split问题
为了对比,直接在cpp文件中main函数中用split做三通道分离,再打印B通道像素值又发现其为32,96,160,224”这四个值。
于是怀疑是 HistogramND.hpp 文件预处理的问题。
(3)设想3:HistogramND.hpp文件预处理问题
HistogramND.hpp预处理和定义中多次修改后对比后发现此处不存在问题。
于是怀疑是main函数中存在问题。
(4)设想4:cpp文件中main函数问题
经多次验证后发现,在得到输出Mat image后,在用hpp文件对图像进行处理时,是先在cpp文件中使用imwrite("output.jpg",image)函数后,再在hpp文件中使用用imread函数将图片读取后进行处理。
而图片存储为jpg格式后,会进行压缩,以至于图像经过 imwrite 再 imread 之后像素值发生了一定变化。

于是确定最终的问题为图片存储为jpg格式后的压缩导致像素值变化。

四、解决问题:


(1)将图片保存为bmp格式
当图片存储为bmp格式时,不会被压缩,图像像素值也不会被改变。
int main()
{
	Mat image = imread("123.jpg");
	
	imshow("input",image);
	colorReduce(image, 64);

	waitKey(0);

	imshow("output", image);
	imwrite("output.bmp", image);

	run("output.bmp");
	
	waitKey(0);
	return 0;
}


然后再用hpp文件中的直方图统计方法得到如下结果:


(2)程序中读取图像文件直接修改为读取Mat

	//导入图片
	//bool importImage(String path){
	//	image = imread(path);
	bool importImage(Mat path){
		path.copyTo(image);
		if (!image.data)
			return false;
		return true;
	}
void run(Mat path){
//void run(String path){
	HistogramND hist;
	if (!hist.importImage(path)){
		cout << "Import Error!" << endl;
		//return -1;
	}
	hist.splitChannels();
	hist.getHistogram();

	hist.displayHisttogram();
	waitKey(0);
}
int main()
{
	Mat image = imread("123.jpg");
	imshow("input",image);
	
	colorReduce(image, 64);

	waitKey(0);

	imshow("output", image);
	
	run(image);
	
	waitKey(0);
	return 0;
}




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值