C++ OpenCV学习笔记(持续更新)

1、图像的加载、修改与保存

涉及API:

cv::imread();    //读取
cv::imshow();    //显示
cv::cvtColor();  //修改
cv::imwrite();   //保存

扩展图像窗口创建API:cv::namedWindow();
cv::namedWindow需要两个参数,第一个参数是窗口名称,第二个参数是关于窗口操作的关键字(包含:WINDOW_AUTOSIZE会根据图像大小自动设置窗口大小并且生成的窗口大小不能修改;WINDOW_NORMAL此关键字一般使用在跟QT集成以后的程序中,表示允许修改窗口大小)

cv::imread:
参数两个,第一个参数是图像存储的绝对路径,第二个参数读取图像类型(包含:IMREAD_UNCANGED表示加载原图;IMREAD_GRAYSCALE表示将图像作为灰度图像加载进来;IMREAD_COLOR表示原图作为RGB图像加载进来)

cv::imshow:
两个参数,第一个参数是图像窗口名称(可以自动创建),第二个参数是Mat对象名

cv::cvtColor:
三个参数,第一个是需更改的Mat对象名,第二个是用于保存更改后的Mat对象名,第三个参数是修改使用的源和目标色彩空间(如:COLOR_BGR2GRAY表示修改成灰度图像)

cv::imwrite:
使用时包含两个参数,第一个参数是保存图像的绝对路径,第二个参数是需要保存的Mat对象名


2、矩阵的掩膜操作

获取图像像素指针:

CV_Assert(image.depth()==CV_8U)

Mat.ptr(int i = 0)获取像素矩阵的指针,其中索引 i 表示第几行,从0开始计行数
获取当前行指针语句:

const uchar* current = image.ptr<uchar>(row);

获取当前像素点P(row,col)的像素值语句:

p(row,col) = current[col];

像素范围处理saturate_cast<uchar>(重要函数)

saturate_cast<uchar>(-100) //返回0
saturate_cast<uchar>(299) //返回255
saturate_cast<uchar>(100) //返回100

说明:此函数的功能是确保RGB值的范围在0~255之间

什么是图像掩膜操作,掩膜操作实现的是图像对比度调整

opencv提供的掩膜操作(对比度提高)API:filter2D
定义掩膜矩阵:

Mat kernel = (Mat_<char>(3,3)<< 0,-1,0,-1,5,-1,0,-1,0);

API调用举例:filter2D(img, dst, img.depth(), kernel);
说明:第一个参数是操作对象名,第二个参数保存操作后对象名,第三个参数图像深度(使用depth()函数获取了原图像深度),第三个参数是掩膜方法(对应掩膜矩阵)

如何初始化一个零时Mat对象用于存储原图像?
代码:

Mat dst = Mat::zeros(img.size(), img.type()); //zeros方法代表创建RGB为0的纯黑图像,大小和类型与原图像相同

拓展:执行时间的显示
代码:

double t = getTickCount();

/* 代码部分 */
double time = (getTickCount() - t)/getTickFrequency();
// cout << "执行时间: " << time << endl;

3、Mat对象(一种Iplimage更加安全的存储对象,Mat对象的内存空间被自动分配)

Mat对象构造函数:

Mat()
Mat(int rows, int cols, int type)
Mat(Size size, int type)

Mat(int rows, int cols, int type, const Scaler &s)    // 说明:前两个参数分别表示行和列,第三个参数是类型参数(比如CV_8UC3中8表示每个通道占8位,U表示无符号,C表示Char类型,3表示三个通道数),第四个参数是向量表示初始化每个像素值为多少,向量长度对应通道数目一致。

Mat(Size size, int type, const Scaler &s)    //Scaler()用来给像素赋值
Mat(int ndims, const int *sizes, int type)
Mat(int ndims, const int *sizes, int type, const Scaler &s)

说明:拷贝构造函数只会赋值对象头部,使用API–>demo = mat.clone() or mat.copyTo(demo) 才能进行完全复制(包括数据部分)

常用方法:

void copyTo(Mat mat);
void convertTo(Mat dst, int type);
Mat clone();
int channels();
int depth();
bool empty();
uchar* ptr(i = 0);    //备注:查资料详学(读取像素值)
cv::Mat::create(size, type) //create方法创建对象(可指定对象尺寸大小)

两种用法:

M.create(img.size(), img.type()); 
M.create(4,3,CV_8UC2);    M = Scaler(123,123);

定义小数组:(掩膜运用)

Mat kernel = (Mat_<char>(3,3)<< 0,-1,0,-1,5,-1,0,-1,0);

初始化全0图像有多种方法,其中比较特殊的是Mat::zero(size, type);
用法:

Mat m = Mat::zero(img.size(), img.type());
Mat m = Mat::zero(2, 2, CV_8UC1);

拓展:Mat::eye(……)方法,初始化对角线为一的图像矩阵。


4、图像操作

读取像素:
· 读取一个gray像素点的像素值(CV_8UC1)
Scalar intensity = img.at<uchar>(row, col); or Scalar intensity = img.at<uchar>(Point(row, col));

· 读取一个RGB像素点的像素值

Vec3f intensity = img.at<Vec3f>(row, col);
float blue = intensity.val[0];
float green = intensity.val[1];
float red = intensity.val[2];

for(int row = o; row < img.rows; row++)
{
    for(int col = 0; col < img.cols; col++)
    {
        int b = img.at<Vec3b>(row, col)[0];
        int g = img.at<Vec3b>(row, col)[1];
        int r = img.at<Vec3b>(row, col)[2];
    }
}

说明:Vec3b是一种数据结构,放置BGR像素点,3b表示3bit读取,也可以用Vec3f,3f表示以float类型读取,如第一种读取方法。

修改像素:
· 灰度图像

img.at<uchar>(row, col) = 123;

· RGB图像

img.at<Vec3b>(row, col)[0] = 123;    //修改参数B
img.at<Vec3b>(row, col)[1] = 123;    //修改参数G
img.at<Vec3b>(row, col)[2] = 123;    //修改参数R

· 空白图

img = Scalar(100);    //将每个像素点赋值为100

· ROI选择

Rect r(10, 10, 100, 100);
Mat smallimg = img(r);

Vec3b与Vec3f
· Vec3b对应三通道顺序blue、green、red的uchar类型数据
· Vec3f对应三通道float类型数据
· 把CV_8UC1转换到CV32F1实现如下:

img.convertTo(dst, CV_32F1);    //使用API-->convertTo(dst, type);

5、图像混合

· 理论-线性混合操作
g(x) = (1-a)f0(x)+af1(x)
说明:f0(x)表示一个图像,f1(x)表示另一个图像,其中a的取值范围为0~1之间,g(x)表示混合后得到的图像(注意:对图像每个像素的操作)

相关API

cv::addWeighted(inputArray     src1,          //参数1:输入图像Mat - src1
                double         alpha,         //参数2:输入图像src1的alpha值(alpha表示表达式中的a)
           inputArray     src2,          //参数3:输入图像Mat - src2
             double         beta,          //参数4:输入图像src2的alpha值
             double         gamma,         //参数5:gamma值(校验值,使其得到正常图像)
             OutputArray    dst,           //参数6:输出混合图像
             int         dtpye = -1     //dtpye默认不用带入
             )

注意:两张图像大小和类型必须一致才可以使用此API混合

dst(I) = saturate(src1(I) * alpha +src2(I) * beta + gamma);

拓展API:

add(img, dst, dst1);    //直接叠加两个图像像素
multiply(img, dst, dst1, 1.0);    //两个图像像素相乘

6、调整图像亮度和对比度

图像变换可以看作像素变换(点操作)和领域操作(区域操作),调整图像亮度和对比度属于像素变换
g(i,j) = af(i,j) + β (其中a>0,β是增益变量)

再次回顾重要API:
//像素范围处理函数
saturate_cast<uchar>(-100),返回0
saturate_cast<uchar>(299),返回255
saturate_cast<uchar>(100),返回100

//图像数据类型转换
img.convertTo(dst, CV_32F);
说明:如果图像默认bit类型,我们把数据转换成浮点型,提高图像精度可以使处理效果提高。


7、绘制形状和文字

· 使用cv::Pointcv::Scalar
Point表示2D平面上一个点,坐标(x,y)
如下:

Point p;
p.x = 10;
p.y = 8;
Point p1 = Point(x, y );

Scalar表示四个元素的向量
Scalar(a, b, c); //a = Blue, b = green, c = Red表示BGR三个通道

· 绘制线、矩形、圆、椭圆等基本几何形状
API使用:
线–> cv::line(LINE_4LINE_8LINE_AA) //参数表示绘制线的类型,LINE_AA表示反锯齿
椭圆–> cv::ellipse
椭圆API说明:使用语句示例–>

ellipse(img, Point(img.rows/2, img.cols/2), Size(img.rows/5, img.cols/6), 90, 0, 360, color, 2, 8);
/* Point表示椭圆中心坐标,size表示椭圆尺寸,其中两个参数表示长短轴,angle = 90表示顺时针方向旋转角,startAngle = 0表示绘制的起始角度,endAngle = 360表示绘制的终止角度。*/

矩形–> cv::rectangle //五个参数,第一个参数是Mat对象,第二个参数矩形类型,第三个参数颜色,第四个参数线宽(默认1),第五个参数线的类型(默认LINE_8)
圆–> cv::circle
填充–> cv::fillPoly
fillPoly各参数说明:用法–>

Point pts[1][5] = { Point(100,100), Point(100,200), Point(200,200), Point(200,100), Point(100,100) };
const Point* ppts[] = { pts[0] };
int npt[] = { 5 };
fillPoly(img, ppts, npt, 1, Scalar(255,0,255), 8);        
//ppts表示多边形各顶点集合(静态点对象指针数组),npt表示多边形顶点个数,                                
//ncontours = 1表示填充个数

对象:
Rect rect = Rect(x, y, w_len, h_len); //后两个参数分别是宽高

· 随机生成与绘制文本
绘制文本API:cv::putText(Mat&, string, Point, int_fontFace, double_fontScale, Scalar, thickness, lineType, bottomLeftOrigin = false);
代码示例:

putText(img, "Hello OpenCV", Point(100,100), CV_FONT_HERSHEY_COMPLEX, 1.0, Scalar(0,0,255), 1, 8);

说明:int_fontFace表示字体类型,double_fontScale表示字体缩放比例,bottomLeftOrigin默认为FALSE不用管。

随机生成图像(以画线为例)
代码如下:

void RandomLineDemo(Mat& img)
{
    RNG rng(12345);
    Point pt1, pt2;
    Mat dst = Mat::zeros(img.size(), img.type());
    namedWindow("test7", CV_WINDOW_AUTOSIZE);
    for (int i = 0; i < 10000; i++)
    {
        pt1.x = rng.uniform(0, img.cols);
        pt2.x = rng.uniform(0, img.cols);
        pt1.y = rng.uniform(0, img.rows);
        pt2.y = rng.uniform(0, img.rows);
        Scalar color = Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255));
        if (waitKey(50) > 0)
            break;
        imshow("test7", dst);
        line(dst, pt1, pt2, color);
    }
}

说明:RNG是opencv中的随机数类,构造函数指明随机数范围或种子个数,使用uniform(正态分布随机数)方法指定随机数范围,同样的也可以使用gaussian(double sigma)方法生成高斯随机数


8、模糊图像(一)

· 图像的模糊原理

  • Smooth/Blur是图像处理中最简单和常用的操作之一
  • 使用该操作的原因之一就是为了给图像预处理时候降低噪声
  • 使用Smooth/Blur操作其背后是数学的卷积计算:g(i, j) = Σ f( i+k, j+l)h(k, l)
  • 通常这些卷积算子计算都是线性操作,所以又叫线性滤波
  • 归一化盒子滤波(均值滤波)
  • 高斯滤波

· 相关API

  • 均值模糊:
    blur(Mat src, Mat dst, Size(xradius, yradius), Point(-1, -1)); //Point表示中心像素在哪里,(-1, -1)表示默认中心像素

  • 高斯滤波:
    GaussianBlur(Mat src, Mat dst, Size(11, 11), sigmax, sigmay); //sigmax, sigmay是用于调节正态分布图像的参数
    注意:其Size(x, y)中x和y必须是正数而且是奇数,size表示窗口大小


9、模糊图像(二)

· 中值滤波

  • 统计排序滤波器
  • 中值对椒盐噪声有很好的抑制作用
    比如有一个5×5图像:
    123 125 126 130 140
    122 124 126 127 135
    118 120 150 125 134
    119 115 119 123 133
    111 116 110 120 130
    3×3领域像素(坐标为22到44)排序如下:115,119,120,123,124,125,126,127,150
    中值等于:124
    均值等于:125.33

API:medianBlur (Mat src, Mat dst, ksize)
注意:中值模糊的ksize大小必须是大于1而且为奇数 --> ksize表示卷积核大小

· 双边滤波

  • 均值滤波无法克服边缘像素信息丢失缺陷,原因是均值滤波是基于平均权重
  • 高斯模糊部分克服该缺陷,但无法完全避免,原因是没有考虑像素值的不同
  • 高斯双边模糊是边缘保留的滤波方法,避免了边缘信息的丢失,保留了图像轮廓不变

API:bilateralFilter (src, dst, d=15, 150, 3)
说明:d=15为计算半径,半径之内的像素都会被纳入计算,如果该参数提供-1则会根据sigma space参数取计算半径
150表示sigma color,决定多少差值之内像素会被计算
3表示sigma space如果d值大于0则声明无效


10、膨胀与腐蚀

· 膨胀

  • 图像形态学操作:基于形状的一系列图像操作集合,主要是基于集合论基础上的形态学数学
  • 形态学有四个基本操作:腐蚀、膨胀、开、闭
  • 腐蚀和膨胀是图像处理中最基本的形态学操作

说明:膨胀操作跟卷积操作类似,假设有图像A和机构元素B,结构元素B在A上移动,其中B定义其中心为锚点,计算B覆盖下A的最大像素值用来替换锚点像素其中B作为结构体可以是任意形状。而腐蚀跟膨胀操作类似,唯一不同的是以最小值替换锚点重叠下图像的像素值。

相关API:
· getStructuringElement(int shape, Size ksize, Point anchor) //获取结构形状
说明:三个参数分别代表形状(MORPH_RECT MORPH_CROSS MORPH_ELLIPSE)、大小(要求奇数)、锚点(默认是Point(-1,-1)意思就是中心像素)
· dilate(src, dst, kernel) //膨胀
· erode(src, dst, kernel) //腐蚀

拓展:
动态调整结构元素大小 (GUI函数)

 TrackBar -> createTrackbar(constString & trackbarname,
                const String winName,
                int* value,
                int count,
                Trackbarcallback func,
                void* userdata = 0)
//其中最重要的是callback函数功能,如果设置为NULL就是说只有值update,但是不会调用callback的函数。

11、形态学操作(多用于二值图像处理)

· 开操作 - open
说明:图像先腐蚀后膨胀的操作即称为图像的开操作,图像开操作可以去掉小的对象。

· 闭操作 - close
说明: 先膨胀后腐蚀称之为闭操作,可以讲大面积图像中小的缺口给填充。

· 形态学梯度 - Morphological Gradient
说明:图像膨胀后减去原图的腐蚀图像,此种方法又称基本梯度,得到的图像具有梯度效果。

· 顶帽 - top hat
说明:顶帽是原图像与开操作之间的差值图像

· 黑帽 - black hat
说明:黑帽是闭操作图像与源图像的差值图像

API:morphologyEx(src, dst, CV_MOP_BLACKHAT, kernel);
参数说明:
- int OP --> CV_MOP_OPEN/ CV_MOP_CLOSE/ CV_MOP_GRADIENT/ CV_MOP_TOPHAT/ CV_MOP_BLACKHAT (表示形态学操作类型)
- Mat kernel --> 结构元素(选取大小取决于去掉的对象大小)
- int Iteration=1 --> 迭代次数,默认为1

使用实例(开操作):

Mat kernal = getStructuringElement(MORPH_RECT, Size(3, 3), Point(-1, -1));
morphologyEx(src, dst, CV_MOP_OPEN, kernal);
imshow("dst", dst);
waitKey(0);

12、提取水平与垂直线(形态学应用)

· 原理方法
图像形态学操作的时候,可以通过自定义的结构元素实现结构元素对输入图像一些对象敏感、另外一些对象不敏感,这样就会让敏感的对象改变而不敏感的对象保留输出。
通过使用两个最基本的形态学操作:膨胀与腐蚀。使用不同的结构元素实现对输入图像的操作并得到想要的结果。

知识回顾:

  • 膨胀,输出的像素值是结构元素覆盖下输入图像的最大像素值
  • 腐蚀,输出的像素值是结构元素覆盖下输入图像的最小像素值

· 结构元素
膨胀与腐蚀过程是可以使用任意的结构元素,常见的形状:矩形、圆、直线、磁盘形状、砖石形状等各种自定义形状。

· 提取步骤
1 - 输入图像
2 - 灰度变换
3 - 二值化 --> adaptiveThreshold
4 - 定义结构元素
5 - 开操作提取水平与垂直线

相关API - adaptiveThreshold(src, dst, double maxValue, int adaptiveMethod, int thresholdType, int blockSize, double C);
参数说明:
double maxValue - 输入图像最大灰度值
int adaptiveMethod - 自适应阈值方法(ADAPTIVE_THRESH_MEAN_C / ADAPTIVE_THRESH_GAUSSIAN_C)
int thresholdType - 阈值类型(常用 THRESH_BINARY)
int blockSize - 子块大小
double C - 常量,可为正数、负数、0

结构元素定义实例代码:

Mat hline = getStructuringElement(MORPH_RECT, Size(src.cols / 16, 1), Point(-1, -1));
Mat wline = getStructruingElement(MORPH_RECT, Size(1, src.row . 16), Point(-1, -1));

拓展API:bitwist_not(src, src); --> 图像二进制数据“非”操作


13、图像上采样和降采样

· 图像金字塔概念说明
我们在图像处理中常常会调整图像大下,最常见的就是放大和缩小,尽管几何变换也可以实现图像的放大和缩小,但是这里所说的
大小调整是指图像金字塔从下到上分辨率的缩小。一个图像金字塔是一系列图像组成,最底下一张是图像尺寸最大,最上方图像尺
寸最小,从空间上由上向下看就像古埃及金字塔。

图像金字塔分为高斯金字塔和拉普拉斯金字塔,高斯金字塔用来对图像进行降采样,拉普拉斯金字塔用来重建一张图片根据它的上
层降采样图片

高斯金字塔:

  • 高斯金字塔是从底向上,逐层采样得到的

  • 降采样之后图像大小是原图像 M × N 的 M/2 × N/2,就是对原图像删除偶数行与列,即得到采样后的上层图像

  • 高斯金字塔的生成过程分为两步:
    1 - 对当前层进行高斯模糊
    2 - 删除当前层的偶数行与列
    即可得到上一层图像,这样上一层和下一层相比,都只有它大小的1/4

          	{1, 4, 6,4,1;	
           	 4,16,24,16,4;
     1/16 × 6,24,36,24,6;
           	 4,16,24,16,4;
           	 1, 4, 6, 4,1;}
    

高斯不同(DOG):

  • 定义:把同一张图像在不同的参数下做高斯模糊之后的结果相减,得到的输出图像称之为高斯不同。

  • 高斯不同是图像的内在特征,在灰度图像增强、角点检测中经常使用
    代码示例:

    cvtColor(src, gray_src, CV_BGR2GRAY);
    GaussianBlur(gray_src, g1_dst, Size(3, 3), 0, 0);
    GaussianBlur(g1_dst, g2_dst, Size(3, 3), 0, 0);
    subtract(g1_dst, g2_dst, DOGimage, Mat());
    normalize(DOGimage, DOGimage, 255, 0, NORM_MINMAX);    // 还原灰度范围
    imshow("DOG image", DOGimage);
    waitKey(0);

相关API:
· 上采用 --> cv::pyrUp(Mat src, Mat dst, Size(src.cols*2, src.rows*2))
效果:生成的图像是原图在宽和高各放大两倍

· 降采样 --> cv::pyrDown(Mat src, Mat dst, Size(src.col/2, src.rows/2))
效果:生成的图像是原图在宽与高各缩小1/2


14、阈值操作

阈值操作:二值化、反二值化、截断、阈值取零、阈值反取零
API–>cv::threshold(img, dst, thresh, max_value, type);
说明:type参数表示阈值操作类型,可填写cv::THRESH_BINARY, cv::THRESH_BINARY_INV, cv::THRESH_TRUNC, cv::THRESH_TOZERO, cv::THRESH_TOZERO_TNV等。


15、边缘填充

·OpenCV中常用的边缘填充函数为copyMakeBorder();
函数原型:void copyMakeBorder(const Mat &src, Mat& dst, int top, int bottom, int left, int right, int borderType, const Scalar &value=Scalar());
功能:扩充src的边缘,将图像变大,然后以各种外插方式自动填充图像边界,这个函数实际上调用了函数cv::borderInterpolate,这个函数最重要的功能就是为了处理边界,比如均值滤波或者中值滤波中,使用copyMakeBorder将原图稍微放大,然后我们就可以处理边界的情况。
参数说明:
src,dst:原图与目标图像
top,bottom,left,right分别表示在原图四周扩充边缘的大小
borderType:扩充边缘的类型,就是外插的类型,OpenCV中给出以下几种方式

  • BORDER_REPLICATE

  • BORDER_REFLECT

  • BORDER_REFLECT_101

  • BORDER_WRAP

  • BORDER_CONSTANT
    BORDER_REPLICATE:边缘像素复制法
    BORDER_REFLECT_101:对称法,以最边缘像素为轴,对称
    BORDER_CONSTANT:常量法


16、sobel算子锐化和Laplace算子锐化

API:

cv::Sobel(img, dst, depth, dx, dy); // dx和dy分别表示x方向和y方向上的差分阶数
cv::Laplacian(img, dst, depth);


17、Canny边缘检测算法

· Canny算法五步走:

1、高斯模糊 - GaussianBlur

2、灰度转换 - cvtColor

3、计算梯度 - Sobel / Scharr

4、非最大信号抑制

5、高低阈值连接输出二值图像

什么是非最大信号抑制:

图像梯度幅值矩阵中的元素值越大,说明图像中该点的梯度值越大,但这不不能说明该点就是边缘(这仅仅是属于图像增强的过程)。在Canny算法中,非极大值抑制是进行边缘检测的重要步骤,通俗意义上是指寻找像素点局部最大值,将非极大值点所对应的灰度值置为0,这样可以剔除掉一大部分非边缘的点。

img

根据图可知,要进行非极大值抑制,就首先要确定像素点C的灰度值在其8值邻域内是否为最大。图中蓝色的线条方向为C点的梯度方向,这样就可以确定其局部的最大值肯定分布在这条线上,也即出了C点外,梯度方向的交点dTmp1和dTmp2这两个点的值也可能会是局部最大值。因此,判断C点灰度与这两个点灰度大小即可判断C点是否为其邻域内的局部最大灰度点。如果经过判断,C点灰度值小于这两个点中的任一个,那就说明C点不是局部极大值,那么则可以排除C点为边缘。这就是非极大值抑制的工作原理。

注意以下两点:

​ 1)中非最大抑制是回答这样一个问题:“当前的梯度值在梯度方向上是一个局部最大值吗?” 所以,要把当前位置的梯度值与梯度方向上两侧的梯度值进行比较;

​ 2)梯度方向垂直于边缘方向。

​ 但实际上,我们只能得到C点邻域的8个点的值,而dTmp1和dTmp2并不在其中,要得到这两个值就需要对该两个点两端的已知灰度进行线性插值,也即根据图中的g1和g2对dTmp1进行插值,根据g3和g4对dTmp2进行插值,这要用到其梯度方向,这是Canny算法中要求解梯度方向矩阵Thita的原因。

​ 完成非极大值抑制后,会得到一个二值图像,非边缘的点灰度值均为0,可能为边缘的局部灰度极大值点可设置其灰度为128。根据下文的具体测试图像可以看出,这样一个检测结果还是包含了很多由噪声及其他原因造成的假边缘。因此还需要进一步的处理。

在这里插入图片描述

高低阈值连接:

img

· API介绍

cv::Canny(InputArray src,      // 8bit输入图像
         OutputArray edges,    // 输出边缘图像,一般都是二值图像,背景为黑色
         double threshold1,    // 低阈值,常取高阈值的1/2或者1/3
         double threshold2,    // 高阈值
         int aptertureSize,    // Sobel算子size,通常为3×3,取值3
         bool L2gradient       // 选择true表示是L2归一化方法,否则使用L1方法
         )

· 相关代码

#include <opencv2/opencv.hpp>
#include <iostream>
#include <math.h>
using namespace cv;
using std::cout;
using std::endl;

const int t1_value = 50, max_value = 255;
Mat src, dst, gray_src;
void Canny_Demo(int, void*)
{
    Mat edge_output;
    blur(gray_src, gray_src, Size(3, 3), Point(-1, -1), BORDER_DEFAULT);
    Canny(gray_src, edge_output, t1_value, t1_value*2, 3, false);
    dst.create(src.size(), src.type());
    src.copyTo(dst, edge_output);
    // imshow("output", dst);
    imshow("out", edge_output);
}
int main(int argc, char** argv)
{
    src = imread("Path");
    if(!src.data)
    {
        cout << "could not load image" << endl;
        return -1;
    }
    imshow("input", src);
    cvtColor(src, gray_src, CV_BGR2GRAY);
    createTrackbar("Threshold Value", "Result", &t1_value, max_value, Canny_Demo);
    Canny_Demo(0, 0);
    
    waitKey(0);
    return 0;
}

18、霍夫变换 - 直线检测

  • 霍夫变换算法简介:霍夫变换运用两个坐标空间之间的变换,将在一个空间中具有相同形状的曲线或直线映射到另一个坐标空间的一个点上形成峰值,从而把检测任意形状的问题转化为统计峰值问题,在第二空间出现的峰值点通过反算方法再映射回原有空间中就得到需要检测图像上的像素点。

  • 算法实现前提:边缘检测已经完成

  • 霍夫直线检测使用的第二空间是极坐标空间,映射图示如下:

    1、图像坐标系到极坐标系参数空间转化过程

img

    说明:从上面可以看到,参数空间的每个点(ρ,θ)都对应了图像空间的一条直线,或者说图像空间的一个点在参 数空间中就对应为一条曲线。参数空间采用极坐标系,这样就可以在参数空间表示原始空间中的所有直线了。     此时图像空间(直角坐标系x-y)上的一个点对应到参数空间(极坐标系ρ-θ)上是一条曲线,确切的说是一条 正弦曲线。

 

    2、图像空间到极坐标空间的转换过程:

img

图6b 图像空间到极坐标参数空间的转换

    这样就把在图像空间中检测直线的问题转化为在极坐标参数空间中找通过点(r,θ)的最多正弦曲线数的问题。霍 夫空间中,曲线的交点次数越多,所代表的参数越确定,画出的图形越饱满。

  • 相关API介绍

    · 标准的霍夫变换cv::HoughLines从平面坐标转换到霍夫空间,最终输出是(θ, r)表示极坐标空间

    · 霍夫变换直线概率cv::HoughLinesP最终输出是直线的两个点(x0, y0, x1, y1)

    cv::HoughLines(InputArray src,      // 输入图像,必须是8-bit灰度图
                   OutputArray lines,   // 输出的极坐标来表示直线
                   double rho,          // 生成极坐标时候的像素扫描步长
                   double theta,        // 生成极坐标时候角度步长,一般取值CV_PI/180
                   int threshold,       // 阈值,只有获得足够交点的极坐标点才被看成是直线
                   double srn=0,        // 是否应用多尺度霍夫变换,默认值为0表示经典霍夫变换
                   double stn=0,        // 同上
                   double min_theta=0,  // 表示多角度扫描范围0~180之间,默认即可
                   double max_theta=CV_PI
                  ) // 一般情况是有经验的开发者使用,需要自己反变换到平面空间
    cv::HoughLinesP(InputArray src,
                    OutputArray lines,
                    double rho,
                    double theta,
                    int threshold,
                    double minLineLength=0, // 最小直线长度
                    double maxLineGap=0     // 最大间隔
                   ) // 常用API
  • 代码演示

    #include <opencv2/opencv.hpp>
    #include <iostream>
    #include <math.h>
    using namespace cv;
    using std::cout;
    using std::endl;
    ​
    Mat src, src_gray, dst;
    int main(int argc, char** argv)
    {
        src = imread("Path");
        if(!src.data)
        {
            cout << "could not load image" << endl;
            return -1;
        }
        imshow("input", src);
        // 边缘检测及灰度转换
        Canny(src, src_gray, 150, 200);
        cvtColor(src_gray, dst, CV_GRAY2BGR);
        imshow("edge", src_gray);
        // 检测并标注直线
        vector<Vec4f> pLines;
        HoughLinesP(src_gray, pLines, 1, CV_PI/180.0, 10, 0, 10);
        Scalar color = Scalar(0, 0, 255);
        for(size_t i = 0; i < pLines.size(); i++)
        {
            Vec4f hline = pLines[i];
            line(dst, Point(hline[0], hline[1]), Point(hline[2], hline[3]), color, 3, LINE_AA);
        }
        imshow("output", dst);
        
        waitKey(0);
        return 0;
    }
原文地址:https://www.cnblogs.com/horacle/p/13167749.html