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月