《OpenCV图像处理》 第二章 构建图像处理工具

1、基本数据类型

  • OpenCV基本数据类型是Mat,用于存储图像。一副图像被保存为一个 头部 加上一个包含像素数据的内存区
  • 一副图像哟若干个通道。灰度图有一个通道,彩色图像通常有红、绿、蓝三个通道(OpenCV以逆序保存,即蓝、绿、红),还有第四个 透明度(alpha)通道。
  • 可用im.channels()来得到一副图像通道数
  • 用若干个位存储一个像素,位数叫做图像的深度(image depth),通常为8bits。所以通常,灰度图像一个像素1byte,彩色为3byte。
  • 可用im.depth()获取深度,其返回值如下

CV_8U,8位无符号整数(0~255)

CV_8S,8位有符号整数(-128~127)

CV_16U,16位无符号整数(0~65535)

CV_16S,16位有符号整数(-32768~32767)

CV_32S,32位有符号整数(-2147483648~2147483647)

CV_32F,32位浮点数

CV_64F,64位浮点数

  •  通过方法convertTo可用转换深度:im.convertTo(Mat dest,CV_32F)
  • convertTo可能还有两个参数alpha和beta,将每个像素p变为newp=p*alpha+beta。以下代码利用这个公式,把图像的值范围映射到0~255。
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace std;
using namespace cv;

int main(int, char *argv[])
{
	Mat m1 = Mat(100, 100, CV_32FC1);
	//初始化为从0到1*10^6随机数
	randu(m1, 0, 1e6);
	imshow("Ori", m1);

	double minRange, MaxRange;
	Point mLoc, MLoc;
	//求这幅图像中最大和最小值点
	minMaxLoc(m1, &minRange, &MaxRange, &mLoc, &MLoc);

	Mat img1;
	//范围转换
	m1.convertTo(img1, CV_8U, 255.0 / (MaxRange - minRange), -255.0 / minRange);
	imshow("Conv", img1);
	waitKey();

	return 0;
}
  • PointAB:A可以是2或3,B可以是i、f或d。例如:Point3d p; p.x=0; p.y=0; p.z=0;
  • Size:Size s;    s.width=30;    s.height=40;
  • Rect:Rect r;    r.x=r.y=0;    r.width=r.height=100;
  • Scalar:Scalar a;    a[0]=0;    a[1]=0;
  • VecAB:A可以是2、3、4、5或6,B可以是b、s、i、f或d。例如:Vec3b rgb;    rgb[0]=255;

2、像素级访问

①方法1:使用模板函数at<>

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

int main(int, char *argv[])
{
	Mat src1 = imread("test.jpg",IMREAD_GRAYSCALE);
	uchar pixel1 = src1.at<uchar>(0, 0);
	cout << "Value of pixel (0,0):" << (unsigned int)pixel1 << endl;

	Mat src2 = imread("test.jpg", IMREAD_COLOR);
	Vec3b pixel2 = src2.at<Vec3b>(0, 0);
	cout << "B component of pixel(0,0):" << (unsigned int)pixel2[0] << endl;
	getchar();

	return 0;
}

②方法2:使用函数ptr

(ptr函数返回指向图像特定行的一个指针)

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

int main(int, char *argv[])
{
	Mat src1 = imread("test.jpg", IMREAD_GRAYSCALE);
	Mat src2 = imread("test.jpg", IMREAD_COLOR);
	uchar R, G, B;
	for (int i = 0; i < src2.rows; i++) {
		Vec3b* pixrow = src2.ptr<Vec3b>(i);
		for (int j = 0; j < src2.cols; j++) {
			B = pixrow[j][0];
			G = pixrow[j][1];
			R = pixrow[j][2];	
		}
	}
	cout << "B:" << (unsigned int)B << endl;//只看最后一个,不然太多了
	getchar();

	return 0;
}

3、图像常用操作

  • 设置矩阵的值:img.setTo(0);    img.setTo(Scalar(B,G,R));
  • MATLAB风格初始化:Mat m1=Mat::eye(100,100,CV_64F);    Mat m2=Mat::zeros(100,100,CV_8UC1);  Mat m3=Mat::ones(100,100,CV_8UC1)*255;
  • 创建矩阵的一个副本:Mat img1=img.clone();
  • 创建一个(具有掩码)矩阵的副本:img.copy(img1,mask);
  • 图像裁剪:Rect roi(r1,c2,width,height);    Mat img1=img(roi).clone();
  • 调整图像大小:resize(img,imag1,Size(),0.5,0.5);  //变为原来的1/2
  • 翻转图像:flip(imgsrc,imgdst,code); //code=0垂直翻转,code>0水平翻转,code<0垂直和水平翻转
  • 分割通道:Mat channel[3];    split(img,channel);    imshow("B",channel[0]);
  • 合并通道:merge(channel,img);
  • 计算非零像素:int nz=countNonZero(img);

4、算术运算

(Mat图像上运算符被重载,可以执行诸如imgblend=0.2*img1+0.8*img2的运算)

#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;

int main(int, char *argv[])
{
	Mat img1 = imread("test.jpg", IMREAD_GRAYSCALE);
	imshow("Ori", img1);

	//创建掩码图像
	Mat mask(img1.rows, img1.cols, CV_8UC1, Scalar(0, 0, 0));
	circle(mask, Point(img1.rows / 2, img1.cols / 2), 150, 255, -1);
	imshow("Mask", mask);

	//AND计算
	Mat r;
	bitwise_and(img1, mask, r);
	imshow("Dst", r);
	waitKey(0);

	return 0;
}

5、数据持久化

  • 除了读写图像和视频等特定函数外,还有一种更通用的方式来保存/加载数据,称为数据持久化(data persistence)。
  • 数据持久化的主类为aptly,命名为FileStorage,表示磁盘上一个文件,数据被存储为xml或者yaml格式。
  • 写入数据步骤:①调用构造函数FileStorage,使用FileStorage::WRITE值传递一个文件名和标志,数据格式由文件扩展名定义的。②使用运算符<<将数据写入文件,通常写为 字符串 值 对。③用release方法关闭文件
  • 读取数据步骤:①调用构造函数FileStorage,使用FileStorage::READ值传递一个文件名和标志。②使用运算符 [] 或 >>从文件中读取数据。③用release方法关闭文件。
  • 利用数据持久化保存和加载滑动条的值的例子
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;
Mat img1;

void tbl_Callback(int value, void *) {
	Mat temp = img1 + value;
	imshow("main_win", temp);
}

int main(int, char *argv[])
{
	img1 = imread("test.jpg", IMREAD_GRAYSCALE);
	int tbl_value=0;

	//加载滑动条的值
	FileStorage fs1("config.xml",FileStorage::READ);
	tbl_value = fs1["tbl_value"];	//读取数据tbl_value 方法1
	fs1["tbl_value"] >> tbl_value;	//读取数据tbl_value方法2
	fs1.release();

	//创建滑动条
	namedWindow("main_win");
	createTrackbar("brightness", "main_win", &tbl_value, 255, tbl_Callback);
	tbl_Callback(tbl_value, NULL);

	waitKey();

	//退出时保存滑动条的值
	FileStorage fs2("config.xml", FileStorage::WRITE);
	fs2 << "tbl_value" << tbl_value;
	fs2.release();

	return 0;
}

(config.xml文件中的内容)

6、直方图

  • OpenCV中函数void calcHist可以计算图像直方图,函数void equalizeHist可以进行直方图均衡化。
  • 计算直方图函数原型:void calcHist(const Mat* images, int nimages, const int* channels, InputArray mask, OutputArray hist, int dims, const int* histSize, const float** ranges, bool uniform=ture,bool accumulate=false)
  • 参数说明:

    ①images:集合中第一幅图像的地址,可以用于处理一批图像。

    ②nimages:原图像数量。

    ③channels:用来计算直方图的通道列表,通道数从0到2。

    ④mask:可选项,指示直方图中图像像素个数。

    ⑤hist:输出直方图。

    ⑥dims:指示直方图的维数。

    ⑦histSize:每一维度上直方图大小的数组。

    ⑧ranges:每一维度上直方图bin边界维度数组的数组。

    ⑨uniform:默认为ture,表示直方图是均匀分布的。

    ⑩accumulate:默认为false,表示直方图是不累加的。

  • 直方图均衡化函数原型:void equalizeHist(InputArray src,OutputArray dst)。第一个参数是输入图像,第二个参数是均衡化后的输出图像。
  • 用void compareHist(InputArray histImage1,InputArray histImage2,method)进行两个直方图的比较
  • ColourImageEqualizeHist (彩色直方图计算)示例代码
#include<opencv2/highgui/highgui.hpp>
#include<opencv2/imgproc/imgproc.hpp>
#include<iostream>

using namespace std;
using namespace cv;

void histogramcalculation(const Mat &Image, Mat &histoImage);

int main(int, char *argv[])
{
	Mat src, imageq;
	Mat histImage;

	//读取原始图像
	src = imread("test.jpg");

	//将图像分为3部分RGB
	vector<Mat>bgr_planes;
	split(src, bgr_planes);

	//显示原图像
	imshow("Ori", src);

	//计算原始图像每个通道的直方图
	histogramcalculation(src,histImage);

	//显示每个颜色通道的直方图
	imshow("Color Image Histogram", histImage);

	//直方图均衡化应用于每一个通道
	equalizeHist(bgr_planes[0], bgr_planes[0]);
	equalizeHist(bgr_planes[1], bgr_planes[1]);
	equalizeHist(bgr_planes[2], bgr_planes[2]);

	//将均衡化后的图像合并起来
	merge(bgr_planes,imageq);
	//显示
	imshow("Equalized Image", imageq);

	//计算均衡化图像的直方图,并显示.
	histogramcalculation(imageq, histImage);
	imshow("Equalized Color Image Histogram", histImage);

	waitKey();

	return 0;
}


void histogramcalculation(const Mat &Image, Mat &histoImage) {
	int histSize = 255;

	//(对R、G、B)设置范围
	float range[] = { 0,256 };
	const float* histRange = { range };

	bool uniform = true;	bool accumulate = false;

	Mat b_hist, g_hist, r_hist;

	vector<Mat> bgr_planes;
	split(Image, bgr_planes);

	//计算各个直方图
	calcHist(&bgr_planes[0], 1, 0, Mat(), b_hist, 1, &histSize, &histRange, uniform, accumulate);
	calcHist(&bgr_planes[1], 1, 0, Mat(), g_hist, 1, &histSize, &histRange, uniform, accumulate);
	calcHist(&bgr_planes[2], 1, 0, Mat(), r_hist, 1, &histSize, &histRange, uniform, accumulate);

	//为B、G、R绘制直方图
	int hist_w = 512;	int hist_h = 400;
	int bin_w = cvRound((double)hist_w / histSize); //cvRound对double型数进行四舍五入

	Mat histImage(hist_h, hist_w, CV_8UC3, Scalar(0, 0, 0));

	//将结果归一化为[ 0,histImage.rows ](也就是说按比例缩放到合适在图上显示)
	normalize(b_hist, b_hist, 0, histImage.rows, NORM_MINMAX, -1, Mat());
	normalize(g_hist, g_hist, 0, histImage.rows, NORM_MINMAX, -1, Mat());
	normalize(r_hist, r_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(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);

		line(histImage, 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(histImage, 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);
	}
	histoImage = histImage;
}

运行结果

  • ColourImageComparison 示例代码
#include<opencv2/highgui/highgui.hpp>
#include<opencv2/imgproc/imgproc.hpp>
#include<iostream>

using namespace std;
using namespace cv;

void histogram2Dcalculation(const Mat &src, Mat &histo2D);
void histogramRGcalculation(const Mat &src, Mat &histoRG);


int main(int, char *argv[])
{
	Mat src, imageq;
	Mat histImg,histImgeq;
	Mat histHSorg, histHSeq;


	//读取原始图像
	src = imread("test.jpg");

	//将图像分为3部分RGB
	vector<Mat>bgr_planes;
	split(src, bgr_planes);
	
	//显示结果
	namedWindow("Source image", 0);
	imshow("Ori", src);

	//计算原始图像的直方图
	histogram2Dcalculation(src, histImg);

	//显示每个颜色通道的直方图
	imshow("H-S Histogram", histImg);

	/****均衡化图像*****/
	//对每个通道进行直方图均衡化
	equalizeHist(bgr_planes[0], bgr_planes[0]);
	equalizeHist(bgr_planes[1], bgr_planes[1]);
	equalizeHist(bgr_planes[2], bgr_planes[2]);

	//合并均衡后的通道
	merge(bgr_planes, imageq);
	//显示
	namedWindow("Equalized Image", 0);
	imshow("Equalized Image",imageq);

	//计算H通道和S通道的二维直方图
	histogram2Dcalculation(imageq,histImgeq);
	//显示
	imshow("H-S Histogram Equalized",histImgeq);

	histogramRGcalculation(src, histHSorg);
	histogramRGcalculation(imageq, histHSeq);

	//应用直方图比较方法
	for (int i = 0; i < 4; i++) {
		int compare_method = i;
		double orig_orig = compareHist(histHSorg, histHSorg, compare_method);
		double orig_equ = compareHist(histHSorg, histHSeq, compare_method);
		cout << "i:" << i << " ORI_ORI:" << orig_orig << " ORI_EQU:" << orig_equ << endl;
	}
	cout << "End" << endl;
	waitKey();

	return 0;
}

void histogram2Dcalculation(const Mat & src, Mat &histo2D)
{
	Mat hsv;
	//转到HSV
	cvtColor(src, hsv, CV_BGR2HSV);
	
	//量化色度为30~255阶
	//饱和度为32~255阶
	int hbins = 255, sbins = 255;
	int histSize[] = { hbins,sbins };
	
	//色度变化从0到179
	float hranges[] = { 0,180 };

	//饱和度变化从0(黑-灰-白)到255(纯光谱颜色)
	float sranges[] = { 0,256 };
	const float* ranges[] = { hranges,sranges };
	MatND hist, hist2;

	//从第0个通道到第1个通道计算直方图
	int channels[] = { 0,1 };

	calcHist(&hsv, 1, channels, Mat(), hist, 1, histSize, ranges, true, false);
	double maxVal = 0;
	minMaxLoc(hist, 0, &maxVal, 0, 0);

	int scale = 1;
	Mat histImg = Mat::zeros(sbins*scale, hbins*scale, CV_8UC3);

	for (int h = 0; h < hbins; h++) {
		for (int s = 0; s < sbins; s++) {
			float binVal = hist.at<float>(h, s);
			int intensity = cvRound(binVal * 255 / maxVal);
			rectangle(histImg, Point(h*scale, s*scale), Point((h + 1)*scale - 1, (s + 1)*scale - 1), Scalar::all(intensity), CV_FILLED);
		}
	}
	histo2D = histImg;
}

void histogramRGcalculation(const Mat & src, Mat & histoRG)
{
	//红色使用50个bin,绿色使用60个bin
	int r_bins = 50; int g_bins = 60;
	int histSize[] = {r_bins,g_bins};

	//红色变化从0到255,绿色变化从0到255
	float r_ranges[] = { 0,255 };
	float g_ranges[] = { 0,255 };

	const float* ranges[] = { r_ranges,g_ranges };

	//使用第0个通道和第1个通道
	int channels[] = { 0,1 };

	//直方图
	MatND hist_base;

	//为HSV图像计算直方图
	calcHist(&src,1,channels,Mat(),hist_base,2,histSize,ranges,true,false);
	normalize(hist_base, hist_base, 0, 1, NORM_MINMAX, -1, Mat());

	histoRG = hist_base;
}

7、其他

  • 通过getTickCount() 和 getTickFrequency()测量耗时。elapsed=((double)getTickCout()-t0)/getTickFrequency(); t0是一开始获得的TickCount。

8、参考资料

     《OpenCV 图像处理》Gloria Bueno Garcia、Oscar Deniz Suarez、Jose Luis Espinosa Aranda著,刘冰 翻译,机械工业出版社出版,2016年11月

猜你喜欢

转载自blog.csdn.net/weixin_39731083/article/details/81208525