基于C++ OpenCV的图像处理-基础篇

目录

一、图像处理基础篇

1.1创建和显示窗口

1.1.1常用API

1.1.2读图片并显示

1.1.3获取视频并显示

1.1.4视频读取与播放

1.1.5视频录制

1.1.6鼠标事件

 1.1.7滑动条控件

1.2 OpenCV必备核心知识

1.2.1 色彩空间的转换

1.2.2数据结构Mat

1.2.2.1定义

1.2.2.2 属性

1.2.2.3拷贝

1.2.2.4对象的属性:行数、列数及通道数

1.2.2.5通道的分离

1.2.2.6通道的合并

1.2.2.7指针访问像素

1.3 绘制基本图像

1.3.1绘制直线

1.3.2绘制矩形

1.3.3绘制圆形

1.3.4绘制椭圆

1.3.5绘制多边形

1.3.6 多边形填充

1.3.7绘制文本

1.3.8绘制图形的综合应用实战

1.4 图像的运算

1.4.1加法运算

1.4.2图像的减法

1.4.3乘法运算

1.4.4除法运算

1.4.5图像的融合

1.4.6图像的逻辑运算:与或非

1.4.7图像运算的应用实战

1.5图像的基本变换

1.5.1图像的缩放

1.5.2图像的翻转

1.5.3图像的旋转

1.5.4图像的仿射变换

1.5.4.1仿射变化矩阵求解:矩阵法

1.5.4.2仿射变换矩阵求解:三点法

 1.5.5透视变换

1.6图形的滤波(卷积)

1.6.1滤波器分类

 1.6.2低通滤波

1.6.2.1方盒滤波及均值滤波

1.6.2.2高斯滤波(中心滤波)

1.6.2.3中值滤波

1.6.2.4双边滤波(适合于美颜)

1.6.3高通滤波

1.6.3.1Sobel(索贝尔)(高斯)滤波

1.6.3.2Scharr算子滤波

1.6.3.3拉普拉斯算子滤波

1.6.4边缘检测Canny

1.7图形形态学

1.7.1图形的二值化

1.7.2腐蚀

1.7.3膨胀

1.7.4开&闭运算

1.7.5形态学梯度

1.7.6顶帽运算

1.7.7黑帽运算

1.8轮廓与矩形 

 1.8.1查找轮廓

1.8.2绘制轮廓

1.8.3轮廓的面积

1.8.4轮廓的周长

1.8.5多边形逼近与凸包

 1.8.6外接多边形

1.9车辆统计项目实战

1.9.1视频去背景

1.9.2相关代码部分

二、特征检测与分割

2.1特征点检测

2.1.1哈里斯焦点检测

2.1.2Shi-Tomasi角点检测

2.2图像的分割

2.2.1分水岭法

2.2.2GrabCut法

2.3距离变换

2.4连通域


一、图像处理基础篇

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开&闭运算

  1. 开运算:先腐蚀再膨胀,可去除非核心区域的噪点,如黑底白点
  2. 闭运算:先膨胀再腐蚀。可去除核心区域的噪点,如白底黑点

 

 左图:开运算,去黑底白点    右图:闭运算-去白底黑点

//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);

  • 33
    点赞
  • 40
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
C++ OpenCV是一种常用的图像处理库,可以用来进行图像的加载、修改和保存等操作。通过使用OpenCV的函数和方法,可以实现对图像的掩膜操作、对比度调整、均衡化以及形态学操作等。 例如,可以使用imread函数加载图像,如下所示: ```cpp Mat src = imread("C:\\Users\\td\\Desktop\\he.jpeg"); ``` 然后,可以使用cvtColor函数将图像转换为指定的色彩空间,例如将BGR色彩空间转换为BGR555色彩空间: ```cpp Mat output; cvtColor(src, output,CV_BGR2BGR555); ``` 接下来,可以使用imwrite函数保存修改后的图像: ```cpp imwrite("D:/zhubajie.png", output); ``` 除了图像加载和保存,还可以进行其他图像处理操作。例如,可以使用equalizeHist函数对图像进行均衡化处理: ```cpp cvtColor(src, src, CV_BGR2GRAY); Mat dst; equalizeHist(src, dst); ``` 还可以使用morphologyEx函数进行形态学操作,如开操作、闭操作、梯度操作等。例如,可以使用getStructuringElement函数创建结构元素,并通过morphologyEx函数对图像进行黑帽操作: ```cpp Mat kernel = getStructuringElement(MORPH_RECT, Size(11, 11), Point(-1, -1)); morphologyEx(src, dst, CV_MOP_BLACKHAT, kernel); ``` 以上是C++ OpenCV图像处理的一些基本操作,根据实际需求可以使用不同的函数和方法来完成不同的图像处理任务。<span class="em">1</span><span class="em">2</span><span class="em">3</span><span class="em">4</span>
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值