一、先来看一个通过图像遍历进行图像降彩色(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格式后的压缩导致像素值变化。
四、解决问题:
当图片存储为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文件中的直方图统计方法得到如下结果:
//导入图片
//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;
}