OpenCV学习笔记

2017.1.16 17:22:34

使用GitHub进行代码托管

  • 安装 Git for windows
  • 安装 TortoiseGIt(Git的图形化界面)
  • 在Github上创建代码仓库
  • 将本地代码上传到github上
  • 删除代码仓库
  • 用ide把github上的项目打开 — vs2015
  • 建立一个新的变更版本,然后同步到GitHub上 — sync
  • 把这次变更送回给原本的项目 — pull request

2017.1.15 21:17:39

0. OpenCV学习资源

1. OpenCV环境配置 — OpenCV3.10在VS2015中的配置

  • 下载 OpenCV,目前版本是3.2 — 官方下载
  • VS2015中新建win32控制台应用程序
  • 右击项目选择属性
  • 平台选择x64位
  • 选择左侧栏中的VC++目录
  • 右侧选择包含目录,添加头文件,把D:\OpenCV\opencv\build\include和D:\OpenCV\opencv\build\include\opencv和D:\OpenCV\opencv\build\include\opencv2添加进去,并将编辑框中的从父级或项目默认设置继承 勾选上
  • 将库目录添加D:\OpenCV\opencv\build\x64\vc14\lib的链接库
  • 选择左侧的链接器—输入
  • 将右侧的附加依赖项中添加 opencvworld310.lib 和opencvworld310d.lib 两个库文件
  • 配置环境变量,在系统环境变量中添加D:\OpenCV\opencv\build\x64\vc14\bin
  • 以上opencv3.10在VS2015中的配置就结束了
  • 下面是示例代码 :
    #include<opencv2\opencv.hpp>
    using namespace cv;
    int main() {
        Mat picture = imread("a.jpg");//放在与项目目录中的一张图片
        imshow("测试程序", picture);
        waitKey(20150910);
    }
    
  • 注意: 一定是要在平台为x64下(本人电脑是64位),debug调试时候也要用x64,不然在不同平台下配置,可能找不到头文件

注意:VS2015下 OpenCV3系列版本中添加OpenCV_contrib教程

2. VS2015下 OpenCV3系列版本中添加OpenCV_contrib

Opencv_Contrib 下载

Cmake下载

OpenCV下载

  • 用下载好的Cmake工具将OpenCV文件夹下的source文件夹中的源码编译成工程文件至自定义的一个目录下(自己指定文件夹),注意编译器的选择是根据自己VS的版本具体选,本人选的是Visual Studio 14 2015 win64,耐心等待编译完成,完成后会有提示(图为借用)

Camke界面

选择编译器

  • 把之前下载好的opencvcontrib-master中的modules文件夹添加到OPENCVECTERNALMODULESPATH中 来配置额外的编译项的目录,这一步是最重要的,点击confirgure,提示完成后再点击 generate

添加额外目录

  • 以管理员打开VS2015,选择文件菜单下的打开—项目/解决方案,找到之前自定义文件下的opencv.sln打开,加载完成后,生成解决方案(debug/x64下的)
  • 在解决方案管理器中找到CMakeTargets中的INSTALL,右键仅用于项目—仅生成INSTALL(B),执行的结果就是在之前的自定义文件夹下多了install文件夹,此文件下就存放着opencv函数库

INSTALL

生成install

  • 再配置项目的环境和系统的环境变量,可以参考 OpneCV环境配置

Visual Studio常用快捷键

  • ctrl Z — 撤销
  • ctrl y — 反撤销
  • ctrl c — 复制本行
  • ctrl x — 剪切本行
  • ctrl l — 删除本行
  • Alt -> — 代码提示
  • ctrl j — 代码提示
  • ctrl tab — 快速切换窗口
  • ctrl g — 快速切换到某一行
  • ctrl k c — 注释代码
  • ctrl k u — 取消注释
  • shift alt enter — 全屏或退出全屏显示

2. OpenCV文件夹下每个目录的介绍

  • 查看源码目录 — D:\OpenCV\opencv\sources\modules

3.实时调用摄像头

4.OpenCL — 开放运算语言

  • GPU通用运算API
  • 是显卡作通用浮点运算的API。比如视频转码,原来这个工作都是CPU来做的。现在显卡运算能力比较强,这个工作完全可以交给显卡来做。那究竟怎样调用显卡呢?这个工作不需要再由程序员考虑了,因为已经有一个现成的接口了,程序员只要管好转码的算法,然后直接调用OpenCL中现成的指令,这个工作就自动由CPU转交到GPU了。

5.OpenGL —

  • 3D的API

2017.1.16 11:01:33

1.头文件和库文件

  • 头文件是文本文件,库文件是二进制文件(将一些头文件编译打包,形成二进制文件包,其中封装函数接口)
  • 头文件在编译时使用,库文件在连接时使用
  • 头文件中是函数或定义的声明,以及少量内联函数的使用,一般不包括非静态函数的实现
  • 库文件中包含函数的实现
  • 一个库文件均有一组头文件对应
  • 库文件通过头文件向外导出接口.用户通过头文件找到库文件函数的实现的代码从而把这段代码链接到用户的程序中

2.库文件的分类

  • 静态库 — 源代码的编译时使用

    包含函数代码本身,在编译时直接将代码加入道程序中,

  • 动态库 — 程序的运行时使用

    这种库包含函数所在的dll文件和文件中函数位置的信息(入口),代码由运行时加载在进程空间中的dll提供,即dll库

  • 如果只有lib文件,那么这个lib文件时静态编译出来的,索引和实现都在其中
  • 动态链接的情况下,一个是lib文件,一个是dll文件 ,lib包含被dll导出的函数的位置和名称,dll则包实际的函数和数据,应用程序使用lib文件链接到dll文件,在应用程序的可执行可执行文件中存放的是dll中相应函数代码的地址,节省内存空间,dll和lib文件必须和应用程序一起发行

3.生成lib库

  • 新建控制台程序没在选择应用程序类型里面,勾选lib,然后添加cpp文件和头文件
  • cpp文件中写具体函数实现
  • #include "libgen.h"
    #include<iostream>
    #include"stdafx.h"
    using namespace std;
    int myadd(int x, int y) {
        int sum;
        sum = x + y;
        return sum;
    }
    
  • 头文件.h中添加函数声明
  •  头文件中代码
     #ifndef _MYADD_H
     #define _MYADD_H
     int myadd(int x,int y);
     #endif
    

4.其他程序使用此lib库

  • 建立一个控制台程序,将头文件和lib库文件库拷贝到工程目录下,然后再头文件里面导入头文件,最后写cpp的代码

5.OpenCV中根据算法的功能将源文件分到多个模块中

  • ,每个模块的源文件编译成一个库文件(如:opencv-core.lib,opencvimgproclib,opencv_highgui.lib等),用户在使用时仅需将库文件添加到自己的项目中,与自己的源文件一起连接成可执行的程序即可

6.图像的表示

  • 灰度图用2维矩阵表示
  • 彩色图像用三维矩阵,每个像素用三个字节表示BGR
  • 用无符号8位整数表示像素亮度

7.创建Mat对象

2017.1.20 11:05:01

操作像素

0. 基本概念

  • Scalar表示像素点的值(实际上是个结构体),Scalar(0)表示将该像素点的像素值设为0(单通道)
  • 图像由矩阵表示(数组存储,一维,二维,三维)
  • 矩阵有维度 —dims
  • 每个维度上的每个点表示像素点 — elemSize() 表示每个像素点占的字节数,total()获得总的像素点个数
  • 像素点有通道(单通道或多通道,多通道最多为4) — channels() 表示每个像素点所占的通道总数(灰度图就是单通道,彩色图就是三通道的)
  • 每个通道又由相应的位数来表示该通道上颜色的深浅 — CV_16SC1表示单通道,每个通道用16位(2个字节)表示,S表示有符号整数,C后面数字表示通道数;CV_64FC3表示三通道,每个通道用64位(8字节)浮点数表示)
  • data:存放矩阵数据内存中的首地址
  • step: 是一个数组,表示总字节数,step[0]表示第二个维度上包含的字节数,,step[1]表示第三维度上包含的字节数,以此类推…
  • step1:是函数,step(0)表示总通道数,step1(0)表示第二维度上的总的通道数,step1(1)表示第三维度上总的通道数
  • elemSize:每个像素点的总的字节数
  • elemSize1:每个通道的字节数
  • depth:每个像素的位数,取值为0到6

enum { CV8U=0, CV8S=1, CV16U=2, CV16S=3, CV32S=4, CV32F=5, CV_64F=6 }

  • 图片
  • 取像素点的各个通道值
    单通道: img.at<uchar>(i,j)
    三通道: img.at<Vec3b>(j,i)[0]
           img.at<Vec3b>(j,i)[1]
           img.at<Vec3b>(j,i)[2]
    
  • 图像按颜色分类:
    二值图:图像颜色只有黑白两种构成 无符号位的单通道
    灰度图:图像颜色由白到黑色之间颜色构成 无符号位的单通道
    彩色图:图像颜色由bgr三原色构成的各种颜色构成 三通道(四通道带有透明度)
    
  • 将彩色图像转换为灰度图的方法:
    方法1:Gray=(R+G+B)/3;
    方法2:Gray=0.299R+0.587G+0.114B;(这种参数考虑到了人眼的生理特点)
    注意1:至于其他格式的彩色图像,可以根据相应的转换关系转为RGB然后再进行灰度化;
    注意2:在编程时要注意图像格式中RGB的顺序通常为BGR
    

1. 载入,显示,保存图像

  • imread(“图像名”,int flag);
    flag:
        \>0:返回3通道的彩色图 特例:如果原图为黑白图,flag=2时,返回的还是黑白图非彩色图
        =0:返回灰度图
        <0:返回原图(带alpha通道) 
    
  • imwrite(“文件名”,要保存图像的Mat对象);
  • imshow(“图像名”,Mat对象);

2. 使用迭代器遍历图像

Mat_<Vec3b>::interator it = image.begin<Vec3b>();
Mat_<Vec3b>:interator itenfd = image.end<Vec3b>();
for(;it!=itend;++it){
    (*it)[0] = (*it)[0]/div*div+div/2;
    (*it)[1] = (*it)[1]/div*div+dov/2;
    (*it)[2] = (it)[2]/div*div+div/23;
}

3.遍历图像和邻域操作

应用实例: 图像的锐化

锐化公式: sharpened_pixel = 5*current-left-right-up-down; //该位置的像素值的5倍减去与其相邻的点的像素值,必须是上下左右都有像素

核矩阵:

图像滤波器:

4. 颜色缩减

  • 例如:CV_8UC3 — 三通道的八位无符号整数表示像素值,像素值一共有256256256种可能

倘若一幅图包含这些像素值,存储起来是很浪费空间的

因此需要缩减颜色种类的数量,也就是像素值的所有可能取值的情况和

  • 具体的颜色缩减的策略:

5. 图像掩模(或掩模)

  • 为8位单通道图像(灰度图或二值图)
  • 掩码某个位置为0,则在此位置上操作不起作用, 掩码某个位置不为0,则在此位置上操作起作用
  • 可以用来提取不规则ROI
  • 示例代码
    #include "opencv2/opencv.hpp"
    using namespace cv;
    /*
    掩码的使用
        掩码: 8位单通道图像
    
    从一幅图中选取感兴趣区域,使用掩码(形状是根据掩码的非零像素形状决定的)
        可以先创建一个像素值全为零的 Mat对象,然后在图像中画长方形,圆形,多边形等 (设置该区域的像素值为255) 作为掩码(即要操作的区域)
    
        如果 按照掩码操作后 得到图像的边缘有问题 ,肯定是边缘部分的黑色不够完善,则此时需要二值化操作,将边缘部分一律变黑
    
        把多通道的图像怎么读成单通道的图像的原理
    
    */
    void main() {
        Mat img = imread("a.png");//背景
        Mat girlImg = imread("girl.jpg");//前景女孩
        Mat mask = imread("mask.jpg",0);//掩码
        Mat mask1 = Mat::zeros(girlImg.size(), CV_8UC1);
        Mat logo = imread("opencv.jpg");
        Mat mask2 = imread("opencv.jpg", 0);
    
        //图像逻辑相加 中 使用 掩码 ,像素值为0的区域不用考虑
        Mat img1 = imread("3.jpg");
        Mat img2 = imread("4.jpg");
        Mat mask3 = Mat::ones(img1.size(), CV_8UC1);
        circle(mask3, Point(mask3.cols / 2, mask3.rows / 2), 100, Scalar(0), -1, 8);
        Mat dst1 = img2.clone();
        add(img1, img2, dst1,mask3);
        namedWindow("dst1", CV_WINDOW_NORMAL);
        imshow("dst1", dst1);
    
    
    
        circle(mask1, Point(mask.rows / 2, mask.cols / 2), 100,Scalar(255), -1, 8);
        //imwrite("mask1,jpg", mask1);
    
        //对mask2像素值取反,255变为0,变成黑色的掩码区域即不用考虑了
        bitwise_not(mask2, mask2);
        namedWindow("mask2",CV_WINDOW_NORMAL);
        imshow("mask2", mask2);
    
        //二值化取反后的mask2, 为了边缘处理
        threshold(mask2, mask2, 100, 255, THRESH_BINARY);
        namedWindow("二值化后的mask2",CV_WINDOW_NORMAL);
        imshow("二值化后的mask2",mask2);
    
        //在背景图片中选取 两块感兴趣区域
        Mat imgROI = img(Rect(106, 128, girlImg.cols, girlImg.rows));
        Mat imgROI1 = img(Rect(0, 0, logo.cols, logo.rows));
    
        //把两张前景图片按照掩码的方式 复制到 感兴趣区域中
        girlImg.copyTo(imgROI,mask1);
        logo.copyTo(imgROI1,mask2);//复制非黑色区域的部分
    
        imshow("dsr", img);
        imwrite("result.png", img);
        waitKey(0);
    }
    
  • 运行结果

运行结果图mask2二值化后的mask2dst1

6. ROI(感兴趣区域) — regin of interest

  • 可以只对感兴趣区域进行图像的处理,例如图像的覆盖,区域图像颜色改变
  • 在某一幅图中选出感兴趣区域
    Mat img;
    Mat imgROI;
    imgOROI = img(Rec(左上角横坐标,左上角纵坐标,宽度,高度);
    或者 imgROI = img(Range(起始行数,终止行数).Range(起始列数,终止列数));
    
    imgROI = img.rowRange(start,end);//创建包含原始图像的特定行
    imgROI = img.colRange(start,end);//创建包含原始图像的特定列
    
    row(i) 是 rowRange(start,end)的特例,返回的是Mat对象
    col(j) 是 colRange(start,end)的特例,返回的是Mat对象
    
  • 在原图中选出感兴趣区域,并用logo这张图进行添加
    1.像素值相加处理
    Mat imgROI;//声明感兴趣区域
    Mat img;//原图像
    imgROI = img(Rec(0,0,logo.cols,logo.rows));//在原图像抠出感兴趣区域(矩形)
    addWeighted(imgROI,1.0,logo,0.3,0.,imgROI);//在感兴趣区域与logo图像直接相加(可能出现像素饱和)
    
    2.像素值的替换
    imgROI = img(Rec(23,34,logo.cols,logo.rows));//声明感兴趣区域
    Mat mask = immread("logo.bmp",0);//加载掩模.必须是灰度图
    logo.copyTo(imgROI,mask);//通过掩模拷贝ROI
    

 

7. 图像的运算

  • 算术运算
    • +
      dst = img1 + img2;
      
    • add()
      add(img1,img2,dst);
      
    • addWeighted()
      addWeighted(img1,0.5,img2,0.5,0,dst);//0.5是各自图像像素值的比重
      
    • dst = img1 -img2;
      
    • substract()
      substract(img1, img1,dst); 像素差小于零按零计算
      
    • absdiff()
      absdiff(img1,img2,dst); 像素差按绝对值计算
      
    • *
      dst = A * img1;
      
    • /
      dst = img1 /A;
      
  • 逻辑运算
    • bitwise_and(srcImg1,srcImg2,outImg,mask);
    • bitwise_or(srcImg1,srcImg2,outImg,mask);
    • bitwise_not(srcImg,outImg,mask);
    • bitwise_xor(srcImg1,srcImg2,outImg,mask);//异或运算,各自图像中独有的部分的并集

8. 图像的几何变换

  • 图像的缩放 — resize()
    void resize(InputArray src, OutputArray dst, Size dsize, double fx=0, double fy=0, int interpolation=INTER_LINEAR )
    
  • 图像的平移
  • 图像的旋转: 通过仿射来实现

2017.1.21 11:04:41

使用直方图统计像素

1.基本概念

  • MatND — 直方图类
  • 将图像二值化函数
  • double threshold(Mat img,Mat imgout,double thresh,double maxval,int type)
    
    第一个参数: 输入数组,单通道的8位或32位浮点类型的Mat(注意:必须是单通道的)
    第二个参数: 函数调用后运算的结果存储在这个Mat对象中
    第三个参数: 阈值的具体值,相当于分界线
    第四个参数: 第五个参数阈值类型取THRESH_BINARY或THRESH_BINARY_INV阈值类型时的最大值
    第五个参数: 阈值类型
    0: THRESH_BINARY  当前点值大于阈值时,取Maxval,也就是第四个参数,下面再不说明,否则设置为0
    
    1: THRESH_BINARY_INV 当前点值大于阈值时,设置为0,否则设置为Maxval
    
    2: THRESH_TRUNC 当前点值大于阈值时,设置为阈值,否则不改变
    
    3: THRESH_TOZERO 当前点值大于阈值时,不改变,否则设置为0
    
    4: THRESH_TOZERO_INV  当前点值大于阈值时,设置为0,否则不改变
    
  • calcHist(Mat img,输入图像个数,通道的索引,掩码 ,返回直方图对象,,每一维的bins的数目,每一维的取值范围);
  • LUT(Mat img,Mat 对照表,Mat result);

2. 绘制直方图

  • 代码
    #include "opencv2/core/core.hpp" 
    #include<opencv2/opencv.hpp>
    #include"opencv2/imgproc/imgproc.hpp"
    #include"opencv2/highgui/highgui.hpp"
    #include  <iostream>
    #include <vector>
    using namespace std;
    using namespace cv;
    
    class Histogram1D {
    private:
        int histSize[1];//项的数量
        float hranges[2];//存储像素最大值和最小值
        int channels[1];//仅用单通道
        const float*  ranges[1];
    
    
    public:
        Histogram1D() {
            histSize[0] = 256;
            hranges[0] = 0.0;
            hranges[1] = 255.0;
            ranges[0] = hranges;
            channels[0] = 0;//默认情况下考察0号通道
        }
        MatND getHistogram(const Mat & img) {
            MatND hist;
            calcHist(
                &img,
                1,//一张图片
                channels,//通道数量
                Mat(),//不使用图像作为掩码
                hist,//返回直方图
                1,//维数
                histSize,//项的数量
                ranges//像素值的范围
            );
            return hist;
        }
        Mat getHistogramImage(const Mat &img) {
            //计算直方图
            MatND hist = getHistogram(img);
            //获取最大值和最小值
            double maxVal = 0;
            double minVal = 0;
            minMaxLoc(hist, &minVal, &maxVal, 0, 0);
            //显示直方图
            Mat histImg(histSize[0], histSize[0], CV_8U, Scalar(255));
            //设置最高点为nbins的90%
            int hpt = (int) (0.9*histSize[0]);
            //每个条目绘制一条垂直线
            for (int h = 0;h < histSize[0];h++) {
                float binVal = hist.at<float>(h);
                int intensity = (int)(binVal*hpt / maxVal);
                line(histImg, Point(h, histSize[0]), Point(h, histSize[0] - intensity), Scalar::all(0));
            }
            return histImg;
        }
    };
    
    int main() {
        Mat img = imread("a.jpg", 0);//以灰度图模式打开
        Histogram1D h;
        MatND histo = h.getHistogram(img);
        for (int i = 0;i < 256;i++) {
            cout << "value" << i << "=" << histo.at<float>(i) << endl;
        }
        namedWindow("Histogram");
        imshow("Histogram", h.getHistogramImage(img));
    
        waitKey(20150901);
        return 0;
    }
    
  • 运行结果图

绘制直方图

  • 从左到右,灰度图,彩色图(小的是直方图),二值图

Markdown

3. 利用查找表修改图像外观

  • 代码
    #include "opencv2/core/core.hpp" 
    #include<opencv2/opencv.hpp>
    #include"opencv2/imgproc/imgproc.hpp"
    #include"opencv2/highgui/highgui.hpp"
    #include  <iostream>
    #include <vector>
    using namespace std;
    using namespace cv;
    
    class Histogram1D {
    private:
        int histSize[1];//项的数量
        float hranges[2];//存储像素最大值和最小值
        int channels[1];//仅用单通道
        const float*  ranges[1];
    
    
    public:
        Histogram1D() {
            histSize[0] = 256;
            hranges[0] = 0.0;
            hranges[1] = 255.0;
            ranges[0] = hranges;
            channels[0] = 0;//默认情况下考察0号通道
        }
        MatND getHistogram(const Mat & img) {
            MatND hist;
            calcHist(
                &img,
                1,//一张图片
                channels,//通道数量
                Mat(),//不使用图像作为掩码
                hist,//返回直方图
                1,//维数
                histSize,//项的数量
                ranges//像素值的范围
            );
            return hist;
        }
        Mat getHistogramImage(const Mat &img) {
            //计算直方图
            MatND hist = getHistogram(img);
            //获取最大值和最小值
            double maxVal = 0;
            double minVal = 0;
            minMaxLoc(hist, &minVal, &maxVal, 0, 0);
            //显示直方图
            Mat histImg(500, 500, CV_8U, Scalar(255));
            //设置最高点为nbins的90%
            int hpt = (int) (0.9*histSize[0]);
            //每个条目绘制一条垂直线
            for (int h = 0;h < histSize[0];h++) {
                float binVal = hist.at<float>(h);
                int intensity = (int)(binVal*hpt / maxVal);
                line(histImg, Point(h, histSize[0]), Point(h, histSize[0] - intensity), Scalar::all(0));
            }
            return histImg;
        }
        Mat applyLookUp(const Mat &img, const Mat &lookup) {
            Mat result;
            LUT(img, lookup, result);
            return result;
        }
    };
    
    int main() {
        Mat img = imread("a.jpg", 0);//以灰度图模式打开
        Mat img0 = imread("a.jpg", 3);//彩色通道打开
        Mat thresholded;
        Mat imgGray;
        Mat histoGram;
        int dim(256);
        Mat lut(1, &dim, CV_8U);
        Mat lookupimg;
        Histogram1D h;
    
        MatND histo = h.getHistogram(img);
        for (int i = 0;i < 256;i++) {
            cout << "value" << i << "=" << histo.at<float>(i) << endl;
        }
        for (int j = 0;j < 256;j++) {
            lut.at<uchar>(j) = 255 - j;
        }
        namedWindow("Histogram");
        namedWindow("TheshHolded");
        namedWindow("img");
        namedWindow("img0");
        namedWindow("lookupimg");
        imshow("img", img);//灰度图
        imshow("img0", img0);//原图
        threshold(img, thresholded, 90, 255, THRESH_BINARY);//二值化图像
        histoGram = h.getHistogramImage(img);//得到图像像素的直方图
        lookupimg = h.applyLookUp(img, lut);
    //cvtColor(img, imgGray, CV_RGB2GRAY);//灰度化图像
    
        imshow("Histogram", histoGram);//直方图
        imshow("ThreahHolded", thresholded);//二值化的图形
        imshow("lookupimg", lookupimg);
    
        waitKey(20150901);
        return 0;
    }
    
  • 第三张图片为查找表修改的结果,产生了负片

Markdown

4. 利用查找表提高图像的对比度 — 色值拉伸

Mat stretch(Mat &img, int minVal = 0) {
        //首先计算直方图
        MatND hist = getHistogram(img);
        //寻找直方图左端
        int imin = 0;
        for (;imin < histSize[0];imin++) {
            cout << hist.at<float>(imin) << endl;
            if (hist.at<float>(imin) > minVal)
                break;
        }
        //寻找直方图的右端
        int imax = histSize[0] - 1;
        for (;imax >= 0;imax--) {
            if (hist.at<float>(imax) > minVal) {
                break;
            }
        }
        //创建查找表
        int dim(256);
        Mat lookup(1, &dim, CV_8U);
        //填充查找表
        for (int i = 0;i < 256;i++) {
            //确保数值位于imin和imax之间
            if (i < imin)
                lookup.at<uchar>(i) = 0;
            else if (i > imax)
                lookup.at< uchar>(i) = 255;
            else
                lookup.at<uchar>(i) = (uchar)(255.0*(i - imin) / (imax - imin) + 0.5);
        }
        //应用查找表
        Mat result;
        result = applyLookUp(img, lookup);
        return result;
}

5. 直方图均衡化 — 图像尽可能平均使用多的像素强度

  • 拉伸像素强度分布范围来增强图像对比度
  • 灰度直方图的均衡化
  • 彩色直方图的均衡化 — 先分离通道逐一均衡化后再合并通道
  • equalizeHist(Mat img,Mat result);//result即为均衡后的图像

6. 疑点

  • int dim(256);//这种定义???
    Mat lut(1,&dim,CV_8U);
    
  • Mat的构造函数

    Mat类常用的函数

人脸检测

  • 代码
    #include<iostream>
    #include<opencv2/opencv.hpp>
    #include<stdio.h>
    using namespace cv;
    using namespace std;
    CascadeClassifier  face_cascade, eyes_cascade;
    String window_name = "Face Detection";
    void detectFaces(Mat frame) {
        std::vector <Rect> faces;
        Mat frame_gray;
        //灰度转换
        cvtColor(frame, frame_gray, COLOR_BGR2GRAY);
        //直方图均衡
        equalizeHist(frame_gray, frame_gray);
        //多尺度人脸检测
        face_cascade.detectMultiScale(frame_gray, faces, 1.1, 3, 0 | CASCADE_SCALE_IMAGE, Size(30, 30));
        //人脸检测结果判定
        for (size_t i = 0;i < faces.size();i++) {
            //检测到人脸中心
            Point center(faces[i].x + faces[i].width / 2, faces[i].y + faces[i].height / 2);
            Mat face = frame_gray(faces[i]);
            std::vector<Rect>eyes;
            //在人脸区域检测人眼
            eyes_cascade.detectMultiScale(face, eyes, 1.1, 2, 0 | CASCADE_SCALE_IMAGE, Size(30, 30));
            if (eyes.size() > 0) {
                //绘制人脸
                ellipse(frame, center, Size(faces[i].width / 2, faces[i].height / 2), 0, 0, 360, Scalar(255, 0, 255), 4, 8, 0);
            }
            imshow(window_name, frame);
        }
    }
    int main() {
        VideoCapture cap(0);
        Mat frame;
        //初始化haar级联人脸分类器xml
        face_cascade.load("haarcascade_frontalface_alt.xml");
        //初始化Haar级联人眼分类器xml
        eyes_cascade.load("haarcascade_eye_tree_eyeglasses.xml");
        while (cap.read(frame)) {
            //人脸检测
            detectFaces(frame);
            if (waitKey(30) >= 0)
                break;
        }
        return 0;
    
    
        }       
    

2017.1.24 10:54:34

1. 直方图的归一化 — normalize()

  • 概念: 如果一幅图像的区域中显示的是一种独特的纹理或一个独特的物体,那么这个区域的直方图可以看做是一个概率函数,它给出了某个像素属于该纹理或物体的概率
  • 实现方法:
    • 选择感兴趣区域(包含目标物体的一份样本)
    • 提取该ROI中的直方图
  • 函数参数
    void normalize(const InputArray src,
     OutputArray dst, 
     double alpha=1, //归一化后的最大值
     double beta=0,//归一化后的最小值
     int normType=NORM_L2, //可取cv:NORM_MINMAX
     int dtype=-1, 
     InputArray mask=noArray())
     ///////////////////////////////////////////////////////////////////
     normType的值可为:
            NORM_MINMAX: 数组的数值被平移或缩放到一个指定的范围,线性归一化,一般较为常用
            NORM_L1: 归一化数组L1-范数(绝对值的和)
            NORM_L2:归一化数组的(欧几里得)L2-范数
    
    dtype: 
            负数时:输出数组的type与输入数组的type相同
            否则:输出数组与输入数组只是通道数相同
    mask:
            操作掩模,用于只是函数是否仅仅对指定的元素进行操作
    

2. 直方图的反投影 — calcBackProject()

 calcBackProject(
    &img,//待处理的图像的mat对象
    1,//一幅图像
    channels,//通道索引
    histogram,//待反投影的直方图
    results,//生成反投影的直方图
    ranges,//每个维度的值域
    255.0)//缩放因子

3. 直方图的对比 — compareHist()

    double compareHist(InputArray H1, InputArray H2, int method)
    double compareHist(const SparseMat& H1, const SparseMat& H2, int method)

    H1: 需要比较的直方图1
    H2: 需要比较的直方图2
    method: 直方图对比的方法, 有如下四种:    
    CV_COMP_CORREL=0,  ---相关性方法(值越大匹配度越高)
    CV_COMP_CHISQR=1,   ---卡方测量法(值越小匹配度越高)
    CV_COMP_INTERSECT=2,   ---直方图相交法(值越大匹配度越高)
    CV_COMP_BHATTACHARYYA =3,   ---Bhattacharyya测量法(小)

比较方式

3. 图像的阈值化(二值化) — threshhold()

threshold(result,result,255*threshold,255,THRESH_BINARY)
    double threshold(Mat img,Mat imgout,double thresh,double maxval,int type)

    第一个参数: 输入数组,单通道的8位或32位浮点类型的Mat(注意:必须是单通道的)
    第二个参数: 函数调用后运算的结果存储在这个Mat对象中
    第三个参数: 阈值的具体值,相当于分界线
    第四个参数: 第五个参数阈值类型取THRESH_BINARY或THRESH_BINARY_INV阈值类型时的最大值
    第五个参数: 阈值类型
        0: THRESH_BINARY  当前点值大于阈值时,取Maxval,也就是第四个参数,下面再不说明,否则设置为0
        1: THRESH_BINARY_INV 当前点值大于阈值时,设置为0,否则设置为Maxval
        2: THRESH_TRUNC 当前点值大于阈值时,设置为阈值,否则不改变
        3: THRESH_TOZERO 当前点值大于阈值时,不改变,否则设置为0
        4: THRESH_TOZERO_INV  当前点值大于阈值时,设置为0,否则不改变

OpenCV模块化简介

  • core — 核心功能模块,包含基本的数据结构和绘图函数
  • imgpro — Image/Process ,包含滤波,几何变换,直方图相关,特征检测,运动分析,形状描述
  • highguii — 高层图像用户界面,包含媒体输入输出,视频捕捉,图像和视频的编码和解码,图像交互界面的接口
  • objdetect — 目标检测模块(级联分类器和SVM)
  • contrib — 新增人脸识别,立体匹配,人工视网膜模型等技术

常用视频格式 和 视频编解码方式

摄像头/视频读取和写入

  • VideoCapture类 — 提供了从摄像机或视频文件捕获视频的C++接口 VideoCapture的构造函数

VideoCapture类中常用的函数

报错未加载opencv_world310.pdb

2017.2.2 21:28:54

1 应用

2. 基本概念

  • 噪点:
  • 核 , 掩码 , 领域算子, 结构元素 之间的区别
  • 插值:  涉及到亚像素取值的问题时会用到插值,例如在图像放大(resize方法中有涉及到) , 图像旋转 , 图像校正
  • 常用的插值算法及其原理
  • 图像放大 和 缩小的原理; 图像放大必然要添加新的像素点,但像素点的像素值怎么确定???  图像缩小必然要删除一些像素点,具体 删除哪些像素点怎么确定???
  • 卷积:某个图像块和某个算子(核)之间进行的运算
  • [平滑: 又称模糊,平滑处理用途有很多,例如减少噪声,平滑处理时需要用到滤波器

2. 图像滤波 — 取出图像噪声

  • 可以通过观察图像(灰度图)中像素值的变化率 来 描述一幅图像 —频域
    • 低频 : 图像区域 像素强度变化缓慢 ; 高频 : 快速变化的图像强度生成的
    • 可以通过 傅里叶变换和余弦变换来显示图像的频谱
    • 由于图像是二维的,分为垂直频率 和 水平频率
  • 通过观察灰度分布 来 描述一幅图像 — 空间域

3. 滤波器 — 低通和高通 滤波器

 3.1 低通滤波器 — 对图像进行模糊或者平滑,减弱边缘处可见的快速变化

  • 方框滤波 — boxFilter()
    void boxFilter(InputArray src, OutputArray dst, int ddepth, Size ksize, Point anchor=Point(-1,-1), bool normalize=true, int borderType=BORDER_DEFAULT )
    
  • 均值滤波器(箱式滤波器,方框滤波器归一化的特例,去噪的同时破坏了图像的细节部分) — blur()
    void blur(InputArray src, OutputArray dst, Size ksize, Point anchor=Point(-1,-1), int borderType=BORDER_DEFAULT )
    
  • 高斯滤波器(加权求均值,权重是由高斯函数确定的) — GuassianBlur()
    void GaussianBlur(InputArray src, OutputArray dst, Size ksize, double sigmaX, double sigmaY=0, int borderType=BORDER_DEFAULT )
    
    • 像素的权重与它离中心像素点距离成正比
  • 中值滤波(非线性滤波,可去除 脉冲噪点 和 椒盐噪点 和 纹理)  — medianBlur()
    void medianBlur(InputArray src, OutputArray dst, int ksize)
    
  • 双边滤波(非线性滤波, 是结合图像空间邻近度和像素值相似度的一种折中处理, 尽量在去噪同时保存边缘 ) — bilaterlFilter()
    void bilateralFilter(InputArray src, OutputArray dst, int d, double sigmaColor, double sigmaSpace, int borderType=BORDER_DEFAULT )
    
  • 实现自己的线性滤波器 — filter2D()
    filter2D(src, dst, ddepth , kernel, anchor, delta, BORDER_DEFAULT );
    
    其中各参数含义如下:
        src: 源图像
        dst: 目标图像
        ddepth: dst 的深度。若为负值(如 -1 ),则表示其深度与源图像相等。
        kernel: 用来遍历图像的核
        anchor: 核的锚点的相对位置,其中心点默认为 (-1, -1) 。
        delta: 在卷积过程中,该值会加到每个像素上。默认情况下,这个值为 0 。
        BORDER_DEFAULT: 这里我们保持其默认值,更多细节将在其他教程中详解
    

 3.2 高通滤波器

  • Sobel滤波器(具有方向性,根据核的不同可以只改变图像的水平和垂直频率) — Sobel() — 检测边缘
    void Sobel(InputArray src, OutputArray dst, int ddepth, int xorder, int yorder, int ksize=3, double scale=1, double delta=0, int borderType=BORDER_DEFAULT )
    
    Sobel算子是一个离微分算子,计算图像灰度函数的近似梯度
    Scharr函数产生的内核结果更加精确
    
    Laplace算子:二维上求二阶偏导的和
    Laplacian( src_gray, dst, ddepth, kernel_size, scale, delta, BORDER_DEFAULT );
    src_gray: 输入图像。
    dst: 输出图像
    ddepth: 输出图像的深度。 因为输入图像的深度是 CV_8U ,这里我们必须定义 ddepth = CV_16S 以避免外溢。
    kernel_size: 内部调用的 Sobel算子的内核大小,此例中设置为3。
    scale, delta 和 BORDER_DEFAULT: 使用默认值。
    

4. 其他形态学变换 — 开闭运算,顶帽,黑帽,形态梯度

  • 形态梯度: 膨胀图和腐蚀图的差,得到原图的轮廓图
  • 开运算: 先腐蚀后膨胀,可以消除亮的小点
  • 闭运算:先膨胀后腐蚀,可以消除黑色的小块

5. 边缘检测

  • Sobel导数
    void Sobel(InputArray src, OutputArray dst, int ddepth, int xorder, int yorder, int ksize=3, double scale=1, double delta=0, int borderType=BORDER_DEFAULT )
    
    参数:
    src: 输入原图像
    dst: 输出图像要求和src一样的尺寸和类型
    
    dx: X方向上的差分阶数
    dy: Y方向上的差分阶数
    ksize: 默认值3, 表示Sobel核大小, 1,3,5,7
    scale: 计算导数值时的缩放因子, 默认值1, 表示不缩放
    delta(δ): 表示在结果存入目标图之前可选的delta值, 默认值0
    borderType: 边界模式, 一般采用默认
    ddepth: 输出图像的深度, 支持如下组合:
    

ddepth

  • Laplace算子
    Laplacian(InputArray src,Output dst,int ddept,int ksize=1,double scale=1,double=0,int borderTpye=BORDER_DEFAULT)
    
    src: 输入原图像(单通道8位图像)
    dst: 输出边缘图像要求和src一样的尺寸和通道数
    ddepth: 目标图像的深度
    Ksize: 用于计算二阶导数的滤波器孔径大小, 须为正奇数, 默认值1
    scale: 可选比例因子, 默认值1
    delta: 可选参数δ, 默认值0
    borderType: 边界模式, 一般采用默认值
    

    SS

  • Canny边缘检测
    void Canny(InputArray image, OutputArray edges, double threshold1, double threshold2, int apertureSize=3, bool L2gradient=false )
    
    参数:
    src: 输入原图像(一般为单通道8位图像)
    dst: 输出边缘图像要求和src一样的尺寸和类型(单通道)
    threshold1: 滞后阈值低阈值(用于边缘连接)
    threshold2: 滞后阈值高阈值(控制边缘初始段)
         推荐高低阈值比值在2:1到3:1之间
    apertureSize: 表示Sobel算子孔径大小, 默认值3
    L2gradient: 计算图像梯度幅值的标识
    

    边缘检的步骤

    • 消除噪声 : 一般使用=高斯平滑滤波器卷积降噪
    • 计算梯度幅值和方向 : 按照Sobel滤波器的步骤操作
    • 非极大值抑制: 排除非边缘像素
    • 滞后阈值: 高阈值和低阈值

6. 霍夫线变换

  • 特征提取技术
  • 检测直线和圆(霍夫线变换和霍夫圆变换)
  • 标准霍夫变换 — HoughLines()
    void HoughLines(InputArray image, OutputArray lines, double rho, double theta, int threshold, double srn=0, double stn=0 )
    
    参数:
    src: 输入原图像(一般为8位单通道二值图像)
    lines: 经过霍夫变换后检测线条的输出矢量, 每一条线由两个元素的矢量(ρ, Θ)表示, 其中ρ是离坐标原点的距离, Θ是弧度线条旋转角度(0表示垂直线, π/2度表示水平线)
    rho: 以像素为单位的距离精度, 另一种表述方式是直线搜索时的进步尺寸的单位半径
    theta: 以弧度为单位的角度精度, 另一种表述方式是直线搜索时的进步尺寸的角度单位
    threshold: 累加平面的阈值参数, 即识别某部分为一条直线时它在累加平面中必须达到的值, 大于阈值threshold的线段才可以被检测通过并返回到结果中
    srn: 默认值0, 对于多尺度的霍夫变换, 这是第三个参数进步尺寸rho的除数距离
    stn: 默认值0, 对于多尺度霍夫变换, 表示单位角度theta
    
  • 累计概率霍夫变换 — HoughLinesP()
    HoughLinesP(InputArray image,OutputArray lines,double rho,double theta,int threshold,double minLineLength=0,double maxLineGap=0)
    
    src: 输入原图像(一般为8位单通道二值图像)
    lines: 经过霍夫变换后检测线条的输出矢量, 每一条线由4个元素矢量(x_1,y_1,x_2,y_2)表示, 其中(x_1,y_1)和(x_2,y_2)是检测到线段的结束点
    rho: 以像素为单位的距离精度, 另一种表述方式是直线搜索时的进步尺寸的单位半径
    theta: 以弧度为单位的角度精度, 另一种表述方式是直线搜索时的进步尺寸的角度单位
    threshold: 累加平面的阈值参数, 即识别某部分为一条直线时它在累加平面中必须达到的值, 大于阈值threshold的线段才可以被检测通过并返回到结果中
    minLineLength: 默认值0, 表示最低线段的长度, 小于则不显示
    maxLineGap: 默认值0, 允许将同一行点与点之间连接起来的最大距离
    

7. 霍夫圆变化 — HoughCircles()

void HoughCircles(InputArray image, OutputArray circles, int method, double dp, double minDist, double param1=100, double param2=100, int minRadius=0, int maxRadius=0 )

    src: 输入原图像(一般为8位单通道图像)
    circles: 经过霍夫变换后检测圆的输出矢量, 每个矢量包含三个元素的浮点矢量(x, y, radius)
    method: 使用的检测方法, 默认只有一种CV_HOUGH_GRADIENT
    dp: 用来检测圆心的累加器图像的分辨率与输入图像之比的倒数, 且此参数允许创建一个比输入图像分辨率低的累加器。如dp=1, 累加器和输入如下具有相同分辨率; dp=2, 累加器只有输入图像一半大的宽度和高度
    minDist: 霍夫变换检测到圆的圆心之间的最小距离, 分辨两个不同圆
    param1: 默认值100, 它是第三个参数method设置的对应参数, 表示传递给Canny边缘算子的高阈值, 低阈值是高阈值的一半
    param2: 默认值100,它是第三个参数method设置的对应参数, 表示检测阶段圆心累加器阈值, 越小, 则会检测到越多不存在的圆, 越大, 更多真正的圆被检测到
    minRadius/maxRadius: 表示圆半径的最小值/最大值

2017.2.5 16:30:34

1. mixChannels()

Mat rgba(100,100,CV_8UC4,Scalar(1,2,3,4));
Mat bgr(rgba.rows,rgba.cols,CV_8UC3);
Mat alpha(rgba.rows,rgba.cols,CV_8UC1);

Mat out[] ={bgr,alpha};
int from_to[] ={0,2, 1,1, 2,0, 3,3};
mixChannels(&rgba,1,out,2,from_to,4);

参数:
    原图,输入图
    原图的个数
    输出图像,如果有多个,就用数组存储
    输出图像的个数
    通道的映射关系
    通道映射的个数

2017.2.6 14:56:54

1. 轮廓的查找与绘制

  • 轮廓的提取 — findContours()

    ① 二值化图像 或者Canny边缘检测

    ② 对原始图像进行被封,查找轮廓的函数会修改原始图像

    ③在黑色背景下查找白色物体的边缘

  • 函数原型
    void findContours(InputOutputArray image, OutputArrayOfArrays contours, OutputArray hierarchy, int mode, int method, Point offset=Point())
    
    参数:
    ①image --- 输入图像,Mat类型8位单通道图像
    
    ②contours ---- 检测到的轮廓,每个轮廓为一个点向量,Point类型的vector
    
    ③hierarchy --- 可选输出变量,包含图像的拓扑信息,轮廓数量的表示,包含了许多元素,每个轮廓contours[i]对应4个hierarchy元素
        注:hierarchy[i][0]~hieerarchy[i][3],分别表示后一轮廓,前一轮廓,父轮廓,内嵌轮廓的索引编号,如果没有对应项,设置为负数
    
    ④mode --- 轮廓检索模式,取值如下
        CV_RETR_EXTERNAL =0 --- 只检测最外层轮廓
        CV_RETR_LIST=1 --- 提取所有轮廓放置在list中,用一条链表存储
        CV_RETR_CCOMP=2 --- 提取所有轮廓并组织为双层结构,顶层是各部分的外部边界,第二层是空洞的边界
        CV_RETR_TREE=3 --- 提取所有轮廓并重新建立网状轮廓结构
    
    ⑤method --- 轮廓的近似方法(很重要)
        CV_CHAIN_APPROX_NONE:连续存储所有的轮廓点,任何两个相邻的点都是水平,垂直,或者斜相邻的
        CV_CHAIN_APPROX_SIMPLE:压缩存储,对于水平,垂直,或者斜项的线段,只会保存端点,比如一个四边形,只会存储四个顶点
        CV_LINK_RUNS
        CV_CHAIN_APPROX_TC89_L1
        CV_CHAIN_APPROX_TC89_KCOS
    
    ⑥offset --- 每个轮廓的可选偏移量,默认值为Point()
    
  • 绘制轮廓 — drawcontours()
    void drawContours(InputOutputArray image, InputArrayOfArrays contours, int contourIdx, const Scalar& color, int thickness=1, int lineType=8, InputArray hierarchy=noArray(), int maxLevel=INT_MAX, Point offset=Point() )   
    
    参数:
    
    ① image --- 待画轮廓的图像
    
    ② contours --- 指向轮廓链表的指针
    
    ③ contoursIndex --- 轮廓绘制的指示索引值,若为负值,则表示绘制所有的轮廓
    
    ④ color --- 绘制轮廓的颜色
    
    ⑤ thickness --- 轮廓线条的粗细值
    
    ⑥ linetype --- 线条类型
    
    ⑦ hierary --- 可选的层次结构
    
    ⑧ maxLevel --- 绘制轮廓的最大等级,默认值为INT_MAX
    
    ⑨ offset --- 可选轮廓偏移参数,默认是Point()
    

轮廓提取

  • 以下是官方文档给出的示例代码

示例代码

  • 代码
    #include "opencv2/opencv.hpp"
    #include <iostream>
    using namespace std;
    using namespace cv;
    
    void main() {
        Mat srcImg = imread("1.jpg");
        Mat tempImg = srcImg.clone();
        imshow("src", tempImg);
        Mat draw(srcImg.size(), CV_8UC3);
        cvtColor(srcImg, srcImg, CV_BGR2GRAY);
    
        threshold(srcImg, srcImg, 100, 255, CV_THRESH_BINARY);
        imshow("srcImg", srcImg);
    
        vector<vector<Point>> contours;//表示轮廓的集合,内部的vector集合表示一个轮廓,外部的vector表示此图像中所有的轮廓
        vector<Vec4i> hierarchy;//表示每个轮廓的相关的轮廓序列号
    
        /*findContours(srcImg, contours, hierarchy, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);
        findContours(srcImg, contours, hierarchy, RETR_LIST, CHAIN_APPROX_SIMPLE);
        findContours(srcImg, contours, hierarchy, RETR_TREE, CHAIN_APPROX_SIMPLE);*/
        findContours(srcImg, contours, hierarchy, RETR_CCOMP, CV_CHAIN_APPROX_NONE);
        int i = 0;
        //遍历轮廓集合中每一个轮廓,并为其附上不同的颜色
        //如果线宽为负值,则为填充轮廓内所有的区域
        /*
        //遍历轮廓
        for(int i =0 ;i<contours,size();i++){
            drawContours(tempImg,contours,iScalar(rand()%255,rand()%255,rand(0%255,2,8)
        }
        //遍历每个轮廓的每个点
        for(int i =0;i<contours.size();i++){
            for(int j =0;j<contours[i].size();j+=15){
                circle(tempImg,Point(contours[i][j].x,Point(contours[i][j].y),2,Scalar(234,45,56),2,8);
            }
        }
        */
        for (;i >= 0;i =hierarchy[i][0]) {
            //drawContours(tempImg, contours,i, Scalar(rand()*11%255, rand()%255, rand()%255), 2, 8);
    
            for (int j = 0;j< contours[i].size();j+=20) {
                circle(tempImg, Point(contours[i][j].x, contours[i][j].y), 2, Scalar(rand() % 255, rand() % 255, rand() % 255),2, 8);
    
                line(tempImg, Point(10, 10), Point(contours[i][j].x, contours[i][j].y), Scalar(rand() % 255, rand() % 255, rand() % 255), 1, 8);
    
                waitKey(100);
                imshow("contours", tempImg);
            }
        }
    
        cout << "num=" << contours.size() << endl;
        //imshow("contours", tempImg);
    
        waitKey(0);
    
    
    }   
    

程序结果图

2.轮廓的提取和边缘建测的区别

  • 边缘检测是检测数字图像中明暗变换的剧烈程度,不变化不连续的像素点
  • 轮廓检测是检测图像中对象的边界
  • 轮廓一定是封闭的,边缘检测到的边缘不一定是封闭的,
  • 先对图像检测边缘再对边缘进行处理得到图像的轮廓

3. 连通域和连通域的标记(算法)

2017.2.21 16:57:45

1. 根据轮廓画外接矩形和最小外接矩形

  • 外接矩形用 vector boundRect(轮廓的个数) 来存储
  • 最小外接矩形用 vector box(轮廓的个数) 存储
  • 用 boundingRect(轮廓的mat对象) 来找轮廓的外接矩形
  • 用 minAreaRect(轮廓的mat对象) 来找轮廓的最小外接矩形
  • 用 box(轮廓的索引值).points(Point2f rect[4]) 来存储最小外接矩形的四个顶点

2. 检测与匹配角点

  • 什么是角点

    点的局部特征叫关键特征点,兴趣点,角点

    --- 一阶导数(即弧度的梯度)的局部最大所对应的像素点
    --- 两条及两条以上的边缘的角点
    --- 图形中梯度值和梯度方向的变化速率都很高的像素点
    --- 角点处的一阶导数最大,二阶导数为零,指示物体边缘变化不连续的方向
    
  • 角点的识别
  • Harris角点检测器 — cornerHarris() — 不适用尺度缩放
  • Shi-Tomasi算法 — goodFeaturetoTrack()
  • 亚像素角点检测 — cornerSubPix()

    在摄像机标定,三维结构重建方面是一个基本的测量值

3. 特征检测方法