目录
一、图像处理基础篇
1.1创建和显示窗口
1.1.1常用API
API定义:namedWindow(const String &winname,int flags);
显示窗口:imshow();
销毁窗口:destroyAllWindows();
窗口大小:resizeWindow();
1.1.2读图片并显示
Mat mImage=imread("C:/Users/xtf_a/Desktop/QTLearn/OpenCVLearn/01/pear.jpg");
namedWindow("图片",WINDOW_NORMAL);
imshow("图片",mImage);
int iKey=waitKey(0);
destroyAllWindows();
1.1.3获取视频并显示
//打开摄像头常用的API:
//open()—打开视频文件或者摄像头;
//isOpen()—判断读取视频文件是否正确,正确返回true;
//release()—关闭视频流文件;
//Grab()
VideoCapture cap(0); //打开摄像头,0表示摄像头地址
Mat mImage1;
cap.set(CAP_PROP_FRAME_WIDTH, 1920); //设置摄像头的像素
cap.set(CAP_PROP_FRAME_HEIGHT, 1080);
while(true){
cap.read(mImage1);
imshow("video",mImage1);
int ikey=waitKey(5);
if(ikey==13){ //13表示回车键,按下回车键退出
break;
}
}
cap.release();
destroyAllWindows();
1.1.4视频读取与播放
VideoCapture cap("C:/Users/xtf_a/Desktop/QTLearn/OpenCVLearn/01/video.mp4");
Mat mImage1;
cap.set(CAP_PROP_FRAME_WIDTH, 1920);
cap.set(CAP_PROP_FRAME_HEIGHT, 1080);
while(true){
cap.read(mImage1);
mshow("video",mImage1);
int ikey=waitKey(50);
if(ikey==13){
break;
}
}
cap.release();
destroyAllWindows();
return;
1.1.5视频录制
视频录制用到的关键API是VideoWriter、write、release(释放数据并强制输出)。
VideoCapture cap(0);
Mat mImage1;
cap.set(CAP_PROP_FRAME_WIDTH, 1920);
cap.set(CAP_PROP_FRAME_HEIGHT, 1080);
QString qsFileName=QString("./myvideo.avi");
int iRcc=VideoWriter::fourcc('M','J','P','G');
VideoWriter vWriter(qsFileName.toStdString(),iRcc,25,Size(1920,1080));
while(cap.isOpened()) //摄像头是否被打开
{
bool rect=cap.read(mImage1);
//写数据到都多媒体
if(rect) {
imshow("video",mImage1);
vWriter.write(mImage1);
int ikey=waitKey(5);
if(ikey==13){
break;
}
}
}
cap.release();
vWriter.release();
destroyAllWindows();
1.1.6鼠标事件
//设置鼠标回调函数:setMosureCallback(winname,callback,userdata);
//自定义回调函数:callback(event,x,y,flags,userdata); //参数必须一致
//(1)event:鼠标的移动、按下等;
//(2)x,y:鼠标的坐标
//(3)flags:鼠标记录
//头文件定义(全局变量,不属于任何类):
void onMousreCallBack(int iEvent,int iX,int iY,int iFlags,void *param);
static Mat mImagess;
static Point startPoint(0,0);
static Point endPoint(0,0);
static int m_iFlags=0;
//Cpp文件:main函数
mImagess=Mat::zeros(Size(600,400),CV_8UC3);
namedWindow("line");
setMouseCallback("line",onMousreCallBack,(void *)&mImagess);
while(true)
{
imshow("line",mImagess);
int iKey=waitKey(10);
if(iKey==13)
{
break;
}
else if(iKey==108) //l
{
m_iFlags=1;
}
else if(iKey==114) //r
{
m_iFlags=2;
}
else if(iKey==99) //c
{
m_iFlags=3;
}
}
destroyAllWindows();
void onMousreCallBack(int iEvent, int iX, int iY, int iFlags, void *param)
{
if(iEvent==EVENT_LBUTTONDOWN)
{
startPoint.x=iX;
startPoint.y=iY;
}
else if(iEvent==EVENT_LBUTTONUP)
{
endPoint.x=iX;
endPoint.y=iY;
switch(m_iFlags)
{
case 1:
line(mImagess,startPoint,endPoint,Scalar(0,0,255),1);
break;
case 2:
rectangle(mImagess,startPoint,endPoint,Scalar(0,0,255),1);
break;
case 3:
int r=ceil(sqrt((startPoint.x-endPoint.x)*(startPoint.x-endPoint.x)+ (startPoint.y-endPoint.y)*(startPoint.y-endPoint.y)));
circle(mImagess,startPoint,r,Scalar(0,0,255),1);
break;
}
}
}
1.1.7滑动条控件
/*>>函数名称:createTrackbar
(1)参数1: trackbarname, winname
(2)参数2: value,当前值
(3)参数3: count, 最小值为0,最大值为count
(4)参数4: callback, 用户数据
>>getTrackBarPos
(1)参数1: trackbarname,
(2)参数2: winname
(3)返回值*/
namedWindow("trackbar");
//创建trackbar
createTrackbar("R","trackbar",0,255);
createTrackbar("G","trackbar",0,255);
createTrackbar("B","trackbar",0,255);
Mat image=Mat::zeros(Size(800,600),CV_8UC3);
int lastR=0;
int lastG=0;
int lastB=0;
while(true)
{
imshow("trackbar",image);
if(waitKey(10)==13)
{
break;
}
int r=getTrackbarPos("R","trackbar");
int g=getTrackbarPos("G","trackbar");
int b=getTrackbarPos("B","trackbar");
if(lastB !=b || lastG !=g || lastR !=r)
{
lastB=b;
lastR=r;
lastG=g;
int height=image.rows;
int width=image.cols;
for(int row=0;row<height;row++)
{
for(int col=0;col<width;col++)
{
image.at<Vec3b>(row,col)[0]=b;
image.at<Vec3b>(row,col)[1]=g;
image.at<Vec3b>(row,col)[2]=r;
}
}
}
}
destroyAllWindows();
示意图
1.2 OpenCV必备核心知识
1.2.1 色彩空间的转换
- RGB:人眼的色彩空间
- OpenCV默认的色彩空间BGR
- HSV/HSB/HSL
- 视频空间YUV
------>>>>>>HSV空间
- Hue:色相,即色彩,如红色、蓝色。不同角度代表不同的颜色
- Saturation:饱和度,颜色的纯度,圆心白、边缘深
- Value:明度
1.2.2数据结构Mat
1.2.2.1定义
1.2.2.2 属性
字段 | 说明 | 字段 | 说明 |
dims | 维度 | channels | 通道数,RGB是3 |
rows | 行数 | size | 矩阵大小 |
cols | 列数 | type | Dep+dt+chs CV_8UC3 |
depth | 像素的位深(8位) | data | 存放数据 |
1.2.2.3拷贝
//浅拷贝:Mat A=imread(file); Mat B(A);
//深拷贝:Mat clone(); Mat::copyTo();
Mat aImage=imread("C:/Users/xtf_a/Desktop/QTLearn/OpenCVLearn/01/pear.jpg");
//浅拷贝
Mat bImage(aImage);
rectangle(aImage,Rect(0,0,200,200),Scalar(0,0,255),1);
imshow("a",aImage);
imshow("b",bImage);
waitKey(0);
destroyAllWindows();
1.2.2.4对象的属性:行数、列数及通道数
Mat aImage=imread("C:/Users/xtf_a/Desktop/QTLearn/OpenCVLearn/01/pear.jpg");
//图像的大小,行列
int iRow=aImage.size().height;
int iCol=aImage.size().width;
int iType=aImage.type();
1.2.2.5通道的分离
Mat aImage=imread("C:/Users/xtf_a/Desktop/QTLearn/OpenCVLearn/01/pear.jpg");
//图像的大小,行列
std::vector<Mat> splitImage;
split(aImage,splitImage);
int iSize=splitImage.size();
if(iSize!=3){
return;
}
imshow("b",splitImage.at(0));
imshow("g",splitImage.at(1));
imshow("r",splitImage.at(2));
waitKey(0);
destroyAllWindows();
1.2.2.6通道的合并
Mat aImage=imread("C:/Users/xtf_a/Desktop/QTLearn/OpenCVLearn/01/pear.jpg");
//图像的大小,行列
std::vector<Mat> splitImage;
split(aImage,splitImage);
int iSize=splitImage.size();
if(iSize!=3){
return;
}
Mat mergeImage;
std::vector<Mat> mergeImages;
mergeImages.push_back(splitImage.at(2));
mergeImages.push_back(splitImage.at(1));
mergeImages.push_back(splitImage.at(0));
merge(mergeImages,mergeImage);
imshow("RGB",mergeImage);
waitKey(0);
destroyAllWindows();
1.2.2.7指针访问像素
//用指针访问像素的这种方式是利用C语言的操作符[]。这种方法最快,但是略微有点抽象。
Mat aImage=imread("C:/Users/xtf_a/Desktop/QTLearn/OpenCVLearn/01/pear.jpg");
int iRow=aImage.size().height;
int iCol=aImage.size().width;
int iChanels=aImage.channels();
int iTotalCol=iCol*iChanels;
int div=16; //数据位数
for(int i=0;i<iRow;i++)
{
uchar *data=aImage.ptr<uchar>(i); //获取第i行数据的首地址
for(int j=0;j<iTotalCol;j++)
{
data[j]=data[j]/div*div+div/2;
}
}
1.2.2.8 动态地址访问像素
//使用动态地址运算配合at方法的colorReduce函数的代码,简介明了,很直观。
int iRow=aImage.size().height;
int iCol=aImage.size().width;
//int iRow=aImage.rows; //获取行数的方法2
//int iCol=aImage.cols; //获取列数的方法2
int iChanels=aImage.channels();
for(int i=0;i<iRow;i++)
{
for(int j=0;j<iCol;j++)
{
int b=aImage.at<Vec3b>(i,j)[0];
int g=aImage.at<Vec3b>(i,j)[1];
int r=aImage.at<Vec3b>(i,j)[2];
}
}
/*说明:
(1) Vec3b:读取一个RGB像素点的像素值,uchar类型,是一个数组;8U 类型的 RGB 彩色图像可以使用Vec3b。
(2) Vec3f::读取一个RGB像素点的像素值,float类型,是一个数组;
(3)单通道值获取:mMask.at<uchar>(i,j)=255;
*/
Vec3f Intensity=image.at<Vec3f>(x,y);
float bule=Intensity.val[0];
float green=Intensity.val[1];
float red=Intensity.val[2];
1.3 绘制基本图像
1.3.1绘制直线
//API定义:
line(InputOutputArray img, Point pt1, Point pt2, const Scalar& color,
int thickness = 1, int lineType = LINE_8, int shift = 0);
Mat mImage=Mat::zeros(Size(600,400),CV_8UC3);
line(mImage,Point(100,100),Point(300,300),Scalar(255,255,255),2);
imshow("line",mImage);
waitKey(0);
destroyAllWindows();
1.3.2绘制矩形
//API定义:
void rectangle(InputOutputArray img, Rect rec,
const Scalar& color, int thickness = 1,
int lineType = LINE_8, int shift = 0);
//API定义:
void rectangle(InputOutputArray img, Point pt1, Point pt2,
const Scalar& color, int thickness = 1,
int lineType = LINE_8, int shift = 0);
//示例:rectangle(mImage,Point(100,100),Point(300,300),Scalar(255,255,255),1);
1.3.3绘制圆形
//API定义:
void circle(InputOutputArray img, Point center, int radius,
const Scalar& color, int thickness = 1,
int lineType = LINE_8, int shift = 0);
//示例:circle(mImage,Point(200,200),150,Scalar(0,0,255),1);
1.3.4绘制椭圆
//API定义:
void ellipse(InputOutputArray img, Point center, Size axes,
double angle, double startAngle, double endAngle,
const Scalar& color, int thickness = 1,
int lineType = LINE_8, int shift = 0);
void ellipse(InputOutputArray img, const RotatedRect& box, const Scalar& color,
int thickness = 1, int lineType = LINE_8);
//示例:ellipse(mImage,Point(300,200),Size(200,100),0,0,360,Scalar(255,255,255),1);
//说明:类型为-1时表示填充
//椭圆填充:ellipse(mImage,Point(300,200),Size(200,100),0,0,360,Scalar(255,255,255),-1);
LineTypes {
FILLED = -1,
LINE_4 = 4, //!< 4-connected line
LINE_8 = 8, //!< 8-connected line
LINE_AA = 16 //!< antialiased line
}
1.3.5绘制多边形
//API定义:
void polylines(InputOutputArray img, InputArrayOfArrays pts,
bool isClosed, const Scalar& color,
int thickness = 1, int lineType = LINE_8, int shift = 0 );
void polylines(InputOutputArray img, const Point* const* pts, const int* npts,
int ncontours, bool isClosed, const Scalar& color,
int thickness = 1, int lineType = LINE_8, int shift = 0 );
std::vector<Point> nowPoint;
nowPoint.push_back(Point(100,100));
nowPoint.push_back(Point(100,200));
nowPoint.push_back(Point(200,300));
nowPoint.push_back(Point(300,300));
nowPoint.push_back(Point(200,100));
polylines(mImage,nowPoint,true,Scalar(255,255,255),1);
1.3.6 多边形填充
//API定义:
void fillPoly(InputOutputArray img, InputArrayOfArrays pts,
const Scalar& color, int lineType = LINE_8, int shift = 0,
Point offset = Point() );
void fillPoly(InputOutputArray img, const Point** pts,
const int* npts, int ncontours,
const Scalar& color, int lineType = LINE_8, int shift = 0,
Point offset = Point() );
1.3.7绘制文本
//API定义:
void putText( InputOutputArray img, const String& text, Point org,
int fontFace, double fontScale, Scalar color,
int thickness = 1, int lineType = LINE_8,
bool bottomLeftOrigin = false );
//示例:putText(mImage,"Hello Word",Point(100,100),2,1,Scalar(255));
1.3.8绘制图形的综合应用实战
//说明:输入L画直线,R画矩形,C画圆,E键画椭圆
//在类之外定义的全局变量及函数
void onMousreCallBack(int iEvent,int iX,int iY,int iFlags,void *param);
static Mat mImagess;
static Point startPoint(0,0);
static Point endPoint(0,0);
static int m_iFlags=0;
//main函数
mImagess=Mat::zeros(Size(600,400),CV_8UC3);
namedWindow("line");
setMouseCallback("line",onMousreCallBack,(void *)&mImagess);
while(true)
{
imshow("line",mImagess);
int iKey=waitKey(10);
if(iKey==13)
{
break;
}
else if(iKey==108) //l
{
m_iFlags=1;
}
else if(iKey==114) //r
{
m_iFlags=2;
}
else if(iKey==99) //c
{
m_iFlags=3;
}
}
destroyAllWindows();
return;
//回调函数
void onMousreCallBack(int iEvent, int iX, int iY, int iFlags, void *param)
{
if(iEvent==EVENT_LBUTTONDOWN)
{
startPoint.x=iX;
startPoint.y=iY;
}
else if(iEvent==EVENT_LBUTTONUP)
{
endPoint.x=iX;
endPoint.y=iY;
switch(m_iFlags)
{
case 1:
line(mImagess,startPoint,endPoint,Scalar(0,0,255),1);
break;
case 2:
rectangle(mImagess,startPoint,endPoint,Scalar(0,0,255),1);
break;
case 3:
int r=ceil(sqrt((startPoint.x-endPoint.x)*(startPoint.x-endPoint.x)+ (startPoint.y-endPoint.y)*(startPoint.y-endPoint.y)));
circle(mImagess,startPoint,r,Scalar(0,0,255),1);
break;
}
}
}
1.4 图像的运算
图像实际上就是矩阵,其加减乘除运算相当于矩阵的加减乘除预算,即对应的像素点位置进行运算。
1.4.1加法运算
//API定义:
void add(InputArray src1, InputArray src2, OutputArray dst,
InputArray mask = noArray(), int dtype = -1);
Mat aImage=imread("C:/Users/xtf_a/Desktop/QTLearn/OpenCVLearn/01/pear.jpg");
Mat blackImage=Mat::ones(aImage.size(),CV_8UC3)*50;
Mat outImage;
add(aImage,blackImage,outImage);
imshow("original",aImage);
imshow("out",outImage);
waitKey(0);
1.4.2图像的减法
//API定义:
void subtract(InputArray src1, InputArray src2, OutputArray dst,
InputArray mask = noArray(), int dtype = -1);
Mat aImage=imread("C:/Users/xtf_a/Desktop/QTLearn/OpenCVLearn/01/pear.jpg");
Mat blackImage=Mat::ones(aImage.size(),CV_8UC3)*50;
Mat outImage;
add(aImage,blackImage,outImage);
subtract(outImage,blackImage,outImage);
imshow("original",aImage);
imshow("out",outImage);
waitKey(0);
1.4.3乘法运算
//API定义:
void multiply(InputArray src1, InputArray src2,
OutputArray dst, double scale = 1, int dtype = -1)
1.4.4除法运算
//API定义:
void divide(double scale, InputArray src2,OutputArray dst, int dtype = -1);
1.4.5图像的融合
//API定义:
void addWeighted(InputArray src1, double alpha, InputArray src2,
double beta, double gamma, OutputArray dst, int dtype = -1);
//alpha、beta:权重,和为1;
//Gamma:静态权重,全都加上该值;
//另外,只有两个图像的特性一致才能进行融合。
1.4.6图像的逻辑运算:与或非
//非操作 API定义
void bitwise_not(InputArray src, OutputArray dst,InputArray mask = noArray());
//或操作 API定义
void bitwise_or(InputArray src1, InputArray src2,
OutputArray dst, InputArray mask = noArray());
//异或操作 API定义
void bitwise_xor(InputArray src1, InputArray src2,
OutputArray dst, InputArray mask = noArray());
//与操作 API定义
void bitwise_and(InputArray src1, InputArray src2,
OutputArray dst, InputArray mask = noArray());
1.4.7图像运算的应用实战
//项目要求:图片添加Logo
Mat aImage=imread("C:/Users/xtf_a/Desktop/QTLearn/OpenCVLearn/01/pear.jpg");
Mat mLogo=Mat::zeros(Size(200,200),CV_8UC3);
Mat mMask=Mat::zeros(Size(200,200),CV_8UC3); //创建掩码,单通道
//绘制两个正方形
for(int i=20;i<120;i++)
{
for(int j=20;j<120;j++)
{
mLogo.at<Vec3b>(i,j)[0]=0;
mLogo.at<Vec3b>(i,j)[1]=0;
mLogo.at<Vec3b>(i,j)[2]=255;
mMask.at<Vec3b>(i,j)[0]=255;
mMask.at<Vec3b>(i,j)[1]=255;
mMask.at<Vec3b>(i,j)[2]=255;
}
}
for(int i=80;i<180;i++)
{
for(int j=80;j<180;j++)
{
mLogo.at<Vec3b>(i,j)[0]=0;
mLogo.at<Vec3b>(i,j)[1]=255;
mLogo.at<Vec3b>(i,j)[2]=0;
mMask.at<Vec3b>(i,j)[0]=255;
mMask.at<Vec3b>(i,j)[1]=255;
mMask.at<Vec3b>(i,j)[2]=255;
}
}
for(int i=80;i<120;i++)
{
for(int j=80;j<120;j++)
{
mLogo.at<Vec3b>(i,j)[0]=255;
mLogo.at<Vec3b>(i,j)[1]=255;
mLogo.at<Vec3b>(i,j)[2]=255;
}
}
//对Mask进行求反
bitwise_not(mMask,mMask);
//选择Logo要添加的位置
Mat mROI=aImage(Rect(50,50,200,200));
// 区域与掩膜版位置进行与操作,保留中间部分图案
Mat mOut;
bitwise_and(mROI,mMask,mOut);
//Logo叠加
add(mLogo,mOut,mOut);
for(int i=50;i<250;i++)
{
for(int j=50;j<250;j++)
{
aImage.at<Vec3b>(i,j)[0]=mOut.at<Vec3b>(i-50,j-50)[0];
aImage.at<Vec3b>(i,j)[1]=mOut.at<Vec3b>(i-50,j-50)[1];
aImage.at<Vec3b>(i,j)[2]=mOut.at<Vec3b>(i-50,j-50)[2];
}
}
imshow("aImage",aImage);
waitKey(0);
destroyAllWindows();
1.5图像的基本变换
1.5.1图像的缩放
//API定义:
void resize( InputArray src, OutputArray dst,
Size dsize, double fx = 0, double fy = 0,
int interpolation = INTER_LINEAR );
/*
fx -x的缩放因子,fy-y的缩放因子
interpolation -缩放后的差值算法
INTER_NEAREST= 0, 临近差值,速度快,效果差
INTER_LINEAR= 1, 双线性差值,原图中的4个点(默认)
INTER_CUBIC= 2, 三次差值,原图的16个点进行计算
INTER_AREA= 3, 区域差值,效果最好但速度慢
INTER_LANCZOS4= 4,
INTER_LINEAR_EXACT = 5,
INTER_NEAREST_EXACT = 6,
INTER_MAX = 7,
WARP_FILL_OUTLIERS = 8,
*/
Mat sfImage;
cv::resize(aImage,sfImage,Size(aImage.cols/2,aImage.rows/2));
imshow("o",aImage);
imshow("s",sfImage);
1.5.2图像的翻转
//API定义:
void flip(InputArray src, OutputArray dst, int flipCode);
flip(aImage,sfImage,-1);
/*
flipCode的值及定义
0-上下翻转
>0-左右翻转
<0:上下+左右
*/
1.5.3图像的旋转
//API定义:
void rotate(InputArray src, OutputArray dst, int rotateCode);
rotate(aImage,sfImage,ROTATE_90_COUNTERCLOCKWISE);
/*
rotateCode的值及定义
ROTATE_90_CLOCKWISE
ROTATE_180
ROTATE_90_COUNTERCLOCKWISE
*/
1.5.4图像的仿射变换
图像的仿射变换是图像的旋转、缩放、平移的总称。
//API定义:
void warpAffine( InputArray src, OutputArray dst,
InputArray M, Size dsize,
int flags = INTER_LINEAR,
int borderMode = BORDER_CONSTANT,
const Scalar& borderValue = Scalar());
/*
M矩阵:变换矩阵
Dsize:变换后的大小
Flags:插值类型:
borderMode:边缘模式
borderValue:边缘值
*/
1.5.4.1仿射变化矩阵求解:矩阵法
//API定义:
Mat getRotationMatrix2D(Point2f center, double angle, double scale);
/*
Center:变换的中心点
Angle:逆时针旋转的角度
Scale:缩放的因子;
*/
int w=aImage.cols;
int h=aImage.rows;
Mat mm=getRotationMatrix2D(Point2f(w/2,h/2),15,0.5);
warpAffine(aImage,sfImage,mm,aImage.size());
1.5.4.2仿射变换矩阵求解:三点法
//API定义: Mat getAffineTransform( InputArray src, InputArray dst );
//API定义: Mat getAffineTransform( const Point2f src[], const Point2f dst[] );
//Src:表示三个点
//dst:转换后的三点
//推荐:两点都在直线上
Mat mm;
std::vector<Point2f> src;
std::vector<Point2f>dst;
src.push_back(Point2f(200,100));
src.push_back(Point2f(400,100));
src.push_back(Point2f(400,300));
dst.push_back(Point2f(150,150));
dst.push_back(Point2f(350,200));
dst.push_back(Point2f(250,400));
mm=getAffineTransform(src,dst);
warpAffine(aImage,sfImage,mm,aImage.size());
imshow("o",aImage);
imshow("s",sfImage);
waitKey(0);
destroyAllWindows();
1.5.5透视变换
透视变换就是将不正的图进行转换,让图片比较正。
//API定义:
void warpPerspective( InputArray src, OutputArray dst,
InputArray M, Size dsize,
int flags = INTER_LINEAR,
int borderMode = BORDER_CONSTANT,
const Scalar& borderValue = Scalar());
//M也是变换矩阵,求解API
//API定义:
Mat getPerspectiveTransform(InputArray src, InputArray dst,
int solveMethod = DECOMP_LU);
Mat getPerspectiveTransform(const Point2f src[], const Point2f dst[],
int solveMethod = DECOMP_LU);
//这个需要四个点,比如图像四个点。实际中可结合轮廓提取进行。如下示例是自己按照拾取坐标进行转换的。
Mat bImage=imread("C:/Users/xtf_a/Desktop/QTLearn/OpenCVLearn/01/123.png");
Mat outImage;
std::vector<Point2f> src;
std::vector<Point2f>dst;
src.push_back(Point2f(100,1100));
src.push_back(Point2f(2100,1100));
src.push_back(Point2f(0,4000));
src.push_back(Point2f(2500,3900));
dst.push_back(Point2f(0,0));
dst.push_back(Point2f(2300,0));
dst.push_back(Point2f(0,3000));
dst.push_back(Point2f(2300,3000));
Mat mm=getPerspectiveTransform(src,dst);
warpPerspective(bImage,outImage,mm,Size(2300,3000));
imshow("o",bImage);
imshow("s",outImage);
waitKey(0);
destroyAllWindows();
1.6图形的滤波(卷积)
滤波的作用就是将一幅图像通过滤波器得到另一幅图像。其中滤波器又称为卷积核,滤波的过程成为卷积。滤波分为高通和底通滤波,底通滤波用于去噪、平滑,高通滤波用于边缘信息检测。
示例:卷积的效果
- 卷积核:一般卷积核为奇数,如3×3、5×5、7×7,……。奇数保证锚点在正中间。在深度学习中,卷积核越大,看到信息(感受野)就越多,提取的特征约好,同时计算量也就越大。锚点就是卷积核的中心点。
- 边界扩充:当卷积核大于1且不进行边界扩充时,输出的尺寸将相应的缩小。当卷积核以标准的方式进行扩充时,则输出数据的控件尺寸与输入的相等。
//API定义:
void filter2D( InputArray src, OutputArray dst, int ddepth, InputArray kernel,
Point anchor = Point(-1,-1), double delta = 0,
int borderType = BORDER_DEFAULT );
/*
Ddepth:位深,一般填写-1,表示与原图一致;
kernel:滤波的内核;
anchor:锚点,Point(-1,-1)表示锚点为内核中心点;
borderType:边界类型,黑边、映射等。
*/
创建掩膜(内核)矩阵的方法:Mat kernel =(Mat_<char>(3,3)<<0,-1,0,-1,5,-1,0,-1,0);需要确保内核的值为1才能保证亮度保持不变。
1.6.1滤波器分类
序号 | 滤波器类型 | 滤波器名称 | 优势 | 缺点 |
1 | 线性滤波器 | 低通滤波器 | 平滑图像,去除噪音 | |
2 | 高通滤波器 | 边缘增强,边缘提取 | ||
3 | 带通滤波器 | 删除特定频率 | ||
4 | 非线性滤波器 | 中值滤波 | 平滑图像,去除噪音 | |
5 | 最大值滤波 | 寻找最亮点 | ||
6 | 最小值滤波 | 寻找最暗点 |
1.6.2低通滤波
1.6.2.1方盒滤波及均值滤波
方盒滤波,有一个参数,normalize=true, a=1/(w*h),相当于均值滤波。当为false时,a=1。其矩阵为:
//方盒滤波 API定义:
void boxFilter( InputArray src, OutputArray dst, int ddepth,
Size ksize, Point anchor = Point(-1,-1),
bool normalize = true,
int borderType = BORDER_DEFAULT );
//均值滤波 API定义:
void blur( InputArray src, OutputArray dst,
Size ksize, Point anchor = Point(-1,-1),
int borderType = BORDER_DEFAULT );
boxFilter(aImage,outImage,-1,Size(5,5));
blur(aImage,outImage,Size(5,5));
1.6.2.2高斯滤波(中心滤波)
//API定义:
void GaussianBlur( InputArray src, OutputArray dst, Size ksize,
double sigmaX, double sigmaY = 0,
int borderType = BORDER_DEFAULT );
//sigmaX:X方向的方差
//sigmaY:Y方向的方差
//borderType:边缘点插值类型。
//处理效果:可以去掉高斯噪点,但边缘的信息会丢失,整体比较模糊。
1.6.2.3中值滤波
取中间值作为卷积的结果。对胡椒噪音效果比较好,但边缘效果差一些。
图 胡椒噪音示意图及处理效果图
//API定义:
void medianBlur( InputArray src, OutputArray dst, int ksize );
1.6.2.4双边滤波(适合于美颜)
即可以保留边缘信息,又可以保存内部信息。比较适合的方法用于美颜。其原理如下:
//API定义:
void bilateralFilter( InputArray src, OutputArray dst, int d,
double sigmaColor, double sigmaSpace,
int borderType = BORDER_DEFAULT );
//d-核的大小,一般表示直径。其他参数需要时根据需求进行调整。
1.6.3高通滤波
高通滤波可帮助查找图像的边缘,进而进行物体的识别。
- Sobel(索贝尔)(高斯)滤波:先进行高斯滤波,在进行一阶导数,突出边缘信息。可改变卷积核大小,设为-1相当于沙尔滤波。只能检测单方向的边缘,需要求和。
- Scharr(沙尔)滤波:内核固定大小,3*3,其效果比较好。只能检测单方向的边缘,需要求和。
- Laplacian(拉普拉斯)滤波:可一次检测边缘信息,但对噪音比较敏感。一般在使用前需要降噪。
1.6.3.1Sobel(索贝尔)(高斯)滤波
//(1)先向x方向进行求导;
//(2)然后向y方向求导;
//(3)最终结果:|G|=|Gx|+|Gy|
//API定义:
void Sobel( InputArray src, OutputArray dst, int ddepth,
int dx, int dy, int ksize = 3,
double scale = 1, double delta = 0,
int borderType = BORDER_DEFAULT );
Mat outImage,outImage1,outImage2;
Mat bImage=imread("C:/Users/xtf_a/Desktop/QTLearn/OpenCVLearn/01/chess.png");
Sobel(bImage,outImage1,CV_64F,1,0,5); //x方向求导
Sobel(bImage,outImage2,CV_64F,0,1,5); //y方向求导
cv::add(outImage1,outImage2,outImage);
imshow("x",outImage1);
imshow("y",outImage2);
imshow("tt",outImage);
waitKey(0);
destroyAllWindows();
效果图:1-x求导 2-y求导 3-合并
1.6.3.2Scharr算子滤波
//API定义:
void Scharr( InputArray src, OutputArray dst, int ddepth,
int dx, int dy, double scale = 1, double delta = 0,
int borderType = BORDER_DEFAULT );
//也需要先求X、再求Y方向的导数。
1.6.3.3拉普拉斯算子滤波
//优点:可以同时求两个方向的边沿。
//缺点:对噪音比较敏感,一般需要先进行去噪再用拉普拉斯变换。
//API定义:
void Laplacian( InputArray src, OutputArray dst, int ddepth,
int ksize = 1, double scale = 1, double delta = 0,
int borderType = BORDER_DEFAULT );
1.6.4边缘检测Canny
- 使用5×5高斯滤波消除噪声;
- 计算图像梯度方向(0/45/90/135);
- 取局部最大值
- 取阈值计算
//API定义:
void Canny( InputArray dx, InputArray dy, OutputArray edges,
double threshold1, double threshold2,bool L2gradient = false );
void Canny( InputArray image, OutputArray edges, double threshold1,
double threshold2,int apertureSize = 3, bool L2gradient = false );
int cannyThr=50;
double factors=2.5;
Canny(mTempImage,mTempImage,cannyThr,cannyThr*factors);
/*
edges 为计算得到的边缘图像。
mage 为 8 位输入图像。
threshold1 表示处理过程中的第一个阈值。
threshold2 表示处理过程中的第二个阈值。
apertureSize 表示 Sobel 算子的孔径大小。
L2gradient 为计算图像梯度幅度(gradient magnitude)的标识。其默认值为 False。如果为 True,则使用更精确的 L2 范数进行计算(即两个方向的导数的平方和再开方),否则使用 L1 范数(直接将两个方向导数的绝对值相加)。
*/
1.7图形形态学
- 基于图像形态进行处理的一些基本方法;
- 这些处理方法基本是对二进制图形进行处理;
- 卷积核决定这图像处理后的效果。
包含方法:腐蚀与膨胀、开运算、闭运算、顶帽、黑帽
- 腐蚀:让图像变小
- 膨胀:让图像变大
- 开运算:先腐蚀后膨胀
- 闭运算:先膨胀再腐蚀
1.7.1图形的二值化
将图像的每个像素变成两种值,如0,255。全局二值化、局部二值化。
//全局二值化 API定义
double threshold( InputArray src, OutputArray dst,
double thresh, double maxval, int type );
/*
src-输入图像,最好是灰度图
dst-输出图像
thresh-阈值
maxval-设置的最大值
type类型:THRESH_BINARY and #THRESH_BINARY_INV ……
*/
cvtColor(aImage,outImage,COLOR_BGR2GRAY);
threshold(outImage,outImage,20,255,THRESH_BINARY);
//自适应阈值 适合光线不一致的情况,可自适应设置阈值。
//API定义:
void adaptiveThreshold( InputArray src, OutputArray dst,
double maxValue, int adaptiveMethod,
int thresholdType, int blockSize, double C );
/*
maxValue-转换成的最大值
adaptiveMethod-计算阈值的方法
thresholdType-THRESH_BINARY + THRESH_BINARY_INV
blockSize-临近区域的大小
C -常量,应从计算出的平均值或加权平均值中减去。
计算阈值的方法:
ADAPTIVE_THRESH_MEAN_C:计算临近区域的平均值
ADAPTIVE_THRESH_GAUSSIAN_C:高斯窗口的加权平均值
*/
adaptiveThreshold(outImage,outImage,255,ADAPTIVE_THRESH_MEAN_C,THRESH_BINARY,5,0);
adaptiveThreshold(outImage,outImage,255,ADAPTIVE_THRESH_GAUSSIAN_C,THRESH_BINARY,11,0);
1.7.2腐蚀
腐蚀:卷积内核区域全部为1时保留为1,否则为0,相当于瘦身。
//API定义:
void erode( InputArray src, OutputArray dst, InputArray kernel,
Point anchor = Point(-1,-1), int iterations = 1,
int borderType = BORDER_CONSTANT,
const Scalar& borderValue = morphologyDefaultBorderValue() );
Mat kernel=(Mat_<char>(3,3)<<1,1,1,1,1,1,1,1,1);
erode(outImage,outImage1,kernel,Point(-1,-1),1);
//API定义:
Mat getStructuringElement(int shape, Size ksize, Point anchor = Point(-1,-1));
/*
备注:
卷积核分类型包括多种,如矩形、原型、椭圆等。
shape的类型:
MORPH_RECT = 0,
MORPH_CROSS = 1,
MORPH_ELLIPSE = 2
*/
1.7.3膨胀
//卷积区域存在1都变成1,相当于增肥。
//API定义:
void dilate( InputArray src, OutputArray dst, InputArray kernel,
Point anchor = Point(-1,-1), int iterations = 1,
int borderType = BORDER_CONSTANT,
const Scalar& borderValue = morphologyDefaultBorderValue() );
1.7.4开&闭运算
- 开运算:先腐蚀再膨胀,可去除非核心区域的噪点,如黑底白点
- 闭运算:先膨胀再腐蚀。可去除核心区域的噪点,如白底黑点
左图:开运算,去黑底白点 右图:闭运算-去白底黑点
//API定义:
void morphologyEx( InputArray src, OutputArray dst,
int op, InputArray kernel,
Point anchor = Point(-1,-1), int iterations = 1,
int borderType = BORDER_CONSTANT,
const Scalar& borderValue = morphologyDefaultBorderValue() );
/*
op为操作类型,如下:
MORPH_ERODE = 0,
MORPH_DILATE = 1,
MORPH_OPEN = 2, //开运算
MORPH_CLOSE = 3, //闭运算
MORPH_GRADIENT = 4, //梯度运算,原图-腐蚀
MORPH_TOPHAT = 5, //顶帽
MORPH_BLACKHAT = 6, //黑帽
MORPH_HITMISS =7
*/
1.7.5形态学梯度
- 梯度=原图-腐蚀图
- 可用于边沿
- API同上
1.7.6顶帽运算
- 顶帽=原图-开运算
- 作用:类似留下噪点
- API同上
1.7.7黑帽运算
- 黑帽=原图-闭运算
- API同上
1.8轮廓与矩形
1.8.1查找轮廓
- 轮廓:具有相同的颜色或强度的连续的曲线点;
- 作用:可用于图形分析,提取图形,如ROI;
- 步骤:为了检测的准确性,一般需要对图像进行二值化或canny操作;
//API定义:
void findContours( InputArray image, OutputArrayOfArrays contours,
OutputArray hierarchy, int mode,
int method, Point offset = Point());
void findContours( InputArray image, OutputArrayOfArrays contours,
int mode, int method, Point offset = Point());
/*
contours-查找到的所有轮廓
hierarchy-轮廓的层级关系
(1)mode参数
RETR_EXTERNAL = 0, //只检测外侧轮廓
RETR_LIST = 1, //检测的轮廓不建立等级关系,是一个list
RETR_CCOMP = 2, //每层最多两层
RETR_TREE = 3, //按照树形存储轮廓
RETR_FLOODFILL = 4
(2)method参数
CHAIN_APPROX_NONE = 1, //存储轮廓上所有点
CHAIN_APPROX_SIMPLE = 2, //只保留拐角点
CHAIN_APPROX_TC89_L1 = 3,
CHAIN_APPROX_TC89_KCOS = 4
*/
cvtColor(mImage,mTempImage,COLOR_BGR2GRAY);
blur(mTempImage,mTempImage,Size(3,3));
threshold(mTempImage,mTempImage,iThreshold,255,THRESH_BINARY_INV);
//**提取轮廓
std::vector<std::vector<Point>> contours;
std::vector<Point> maxContour;
std::vector<Vec4i>hierarcy;
findContours(mTempImage,contours,hierarcy,CV_RETR_TREE,CV_CHAIN_APPROX_NONE);
1.8.2绘制轮廓
//API定义:
void drawContours( InputOutputArray image, InputArrayOfArrays contours,
int contourIdx, const Scalar& color,
int thickness = 1, int lineType = LINE_8,
InputArray hierarchy = noArray(),
int maxLevel = INT_MAX, Point offset = Point() );
/*
contourIdx:-1表示绘制所有轮廓
contourIdx:-1表示填充,只填充最外面的轮廓进行填充
*/
drawContours(mMaxRectImage,contours,iMaxIndex,Scalar(255,255,255),-1);
1.8.3轮廓的面积
//API定义:
double contourArea( InputArray contour, bool oriented = false );
std::vector<std::vector<Point>> contours;
std::vector<Point> maxContour;
std::vector<Vec4i>hierarcy;
findContours(mTempImage,contours,hierarcy,CV_RETR_TREE,CV_CHAIN_APPROX_NONE);
int areaSize=contours.size();
dMaxArea=0;
int iMaxIndex=0;
for(int i=0;i<areaSize;i++)
{
double area=contourArea(contours[i]);
if(area>dMaxArea)
{
dMaxArea=area;
iMaxIndex=i;
}
}
drawContours(mMaxRectImage,contours,iMaxIndex,Scalar(255,255,255),-1);
1.8.4轮廓的周长
//API定义:
double arcLength( InputArray curve, bool closed );
1.8.5多边形逼近与凸包
//API定义:
void approxPolyDP( InputArray curve,
OutputArray approxCurve,double epsilon, bool closed );
//API定义:
void convexHull( InputArray points, OutputArray hull,
bool clockwise = false, bool returnPoints = true );
//points:轮廓
//hull:凸包点
//clockwise:顺时针绘制
1.8.6外接多边形
绿框:最大外接矩阵 红框:最小外接矩阵
//最小外接矩阵 API定义
RotatedRect minAreaRect( InputArray points ); //返回值RotatedRect
//最小外接矩阵 API定义
Rect boundingRect( InputArray array ); //返回值Rect
1.9车辆统计项目实战
- 加载视频
- 通过形态学识别车辆
- 对车辆进行统计
- 显示车辆信息
1.9.1视频去背景
//API定义:
Ptr<BackgroundSubtractorMOG2> createBackgroundSubtractorMOG2(int history=500,
double varThreshold=16,
bool detectShadows=true);
//去除非运动的背景。
1.9.2相关代码部分
int minWidth=80;
int minHeigth=80;
int lineHeight=600;
int iOffset=6;
int iCount=0;
//加载视频
VideoCapture cap("C:/Users/xtf_a/Desktop/QTLearn/OpenCVLearn/01/video.mp4");
Mat currentImage;
//去除背景
Ptr<BackgroundSubtractorMOG2> bgsubmog=createBackgroundSubtractorMOG2();
//形态学kernel
Mat kernel=getStructuringElement(MORPH_RECT,Size(5,5));
while(true)
{
if(!cap.read(currentImage))
{
break;
}
Mat grayImage;
//转化为灰度
cvtColor(currentImage,grayImage,COLOR_BGR2GRAY);
//高斯去噪
GaussianBlur(grayImage,grayImage,Size(7,7),3);
//去除背景:运动物体是前景,不动的是背景
Mat mask;
bgsubmog->apply(grayImage,mask);
//腐蚀:去掉图中的黑底白色小方块
erode(mask,mask,kernel);
//膨胀:还原放大
dilate(mask,mask,kernel,Point(-1,-1),3);
//闭操作:去掉物体内部白底黑色的小方块
morphologyEx(mask,mask,MORPH_CLOSE,kernel);
morphologyEx(mask,mask,MORPH_CLOSE,kernel);
//查找轮廓
std::vector<std::vector<Point>> contoursss;
std::vector<Point> currentCT;
findContours(mask,contoursss,RETR_TREE,CHAIN_APPROX_NONE);
for(int i=0;i<contoursss.size();i++)
{
currentCT.clear();
currentCT=contoursss.at(i);
Rect nowRect=boundingRect(currentCT);
line(currentImage,Point(10,lineHeight),Point(1200,lineHeight),Scalar(0,0,255),2);
if(nowRect.width>minWidth && nowRect.height>minHeigth)
{
rectangle(currentImage,nowRect,Scalar(0,0,255),2);
int iY=nowRect.y+nowRect.height/2;
if(iY>lineHeight-iOffset && iY<lineHeight+iOffset )
{
iCount++;
}
}
}
QString qsCount=QString("Cars Count:%1").arg(iCount);
putText(currentImage, qsCount.toStdString(), Point(500,60), FONT_HERSHEY_SIMPLEX, 2, Scalar(0,0,255),5);
imshow("video",currentImage);
if(waitKey(20)==13)
{
break;
}
}
cap.release();
destroyAllWindows();
二、特征检测与分割
2.1特征点检测
2.1.1哈里斯焦点检测
Harris点:超一个方向移动,如出现变化,则说明为边沿或角点。光滑地区,无论向哪个方向移动,衡量系数不变;边缘地址,垂直边缘移动时,衡量系统变化剧烈。在检查点处,任何方向移动都发生变化。
//API定义:
void cornerHarris( InputArray src, OutputArray dst, int blockSize,
int ksize, double k,
int borderType = BORDER_DEFAULT );
//blockSize: 检测窗口的大小
//ksize: sobel的卷积核
//k:权重系数,经验值,一般取0.02-0.04之间。
//注意:必须是灰度图
2.1.2Shi-Tomasi角点检测
对Harris进行了优化,将k从经验值,不用设。
//API定义:
void goodFeaturesToTrack( InputArray image, OutputArray corners,
int maxCorners, double qualityLevel, double minDistance,
InputArray mask, OutputArray cornersQuality, int blockSize = 3,
int gradientSize = 3, bool useHarrisDetector = false, double k = 0.04);
/*
maxCorners:角点的最大数,值为0表示无限制
qualityLevel:小于1的证书,一般在0.01-0.1之间
minDistance:角之间最小欧式距离
mask:感兴趣区域
gradientSize : 检测窗口的大小
useHarrisDetector:是否使用Harris
*/
2.2图像的分割
2.2.1分水岭法
对于灰度图,通过极值点进行检测。但是,图像存在多个极小区域,而产生许多的集水盆。其过程为:(1) 标记背景; (2) 标记前景 (3) 标记位置区域 (4) 进行分割
//API定义:
void watershed( InputArray image, InputOutputArray markers );
2.2.2GrabCut法
- 用户指定前景的大体区域,剩下的背景区域;
- 用户还可以明确指出某些地方为前景或者背景;
- GrabCut采用分段迭代的方法分析前景物体形成模树;
- 根据权重决定某个像素是前景还是背景;
2.3距离变换
//API定义:
void distanceTransform( InputArray src, OutputArray dst,
int distanceType, int maskSize, int dstType=CV_32F);
2.4连通域
//API定义:
connectedComponents(InputArray image, OutputArray labels,
int connectivity = 8, int ltype = CV_32S);