1 膨胀
2 腐蚀
getStructuringElement(int shape, Size ksize, Point anchor);
int shape
形状: MORPH_RECT \ MORPH_CROSS \ MORPH_ELLIPSESize ksize
大小: kernelPoint anchor
锚点: 默认是Point(-1, -1)是中心像素dilate(Mat src, Mat dst, kernel)
d s t ( x , y ) = max ( x ′ , y ′ ) : e l e m e n t ( x ′ , y ′ ) ≠ 0 s r c ( x + x ′ , y + y ′ ) dst(x,y)=\max_{(x',y'):element(x',y')\neq0}src(x+x',y+y') dst(x,y)=(x′,y′):element(x′,y′)=0maxsrc(x+x′,y+y′)
erode(Mat src, Mat dst, kernel)
d s t ( x , y ) = min ( x ′ , y ′ ) : e l e m e n t ( x ′ , y ′ ) ≠ 0 s r c ( x + x ′ , y + y ′ ) dst(x,y)=\min_{(x',y'):element(x',y')\neq0}src(x+x',y+y') dst(x,y)=(x′,y′):element(x′,y′)=0minsrc(x+x′,y+y′)
createTrackbar(const String& trackbarname, const String winName, int* value, int count, Trackbarcallback func, void* userdata=0);
其中最重要的是callback函数功能。如果设置为NULL就是说只有值更新,但是不会调用callback的函数。
callback函数形式为(int, void*) typedef void (*TrackbarCallback)(int pos, void* userdata);
highgui.hpp源码中对参数的解释:
@param trackbarname Name of the trackbar.
@param winname Name of the window that is the parent of the trackbar.
部分代码示例:
#include
#include
using namespace std;
using namespace cv;
int kernel_size = 3;
int max_k_size = 11;
void callback(int, void*);
int main(int argc, char** argv)
{
Mat src, dst;
src = imread("F:/opencv-4.0.0/etc/Image_collection/nana.jpg");
imshow("nana", src);
namedWindow("nana_process", WINDOW_AUTOSIZE);
createTrackbar("BAR", "nana_process", kernel_size, max_k_size, callback);
//第一个参数为bar的名字,第二个参数是bar显示到的窗口名,第三个参数是bar改动参数的名字,第四个参数是最大改动范围,第五个是回调函数
callback(0, 0);
return 0;
}
void callback(int, void*)
{
Mat kernel = getStructuringElement(MORPH_RECT, Size(kernel_size, kernel_size), Point(-1, -1));
dilate(src, dst, kernel);
//erase(src, dst, kernel);
imshow("nana_process", dst);
}
1 开操作- open
2 闭操作- close
3 形态学梯度- Morphological Gradient
4 顶帽- top hat
5 黑帽- black hat
morphologyEx(src, dst, CV_MOP_OPEN, kernel);
Mat src
—— 输入图像Mat dst
—— 输出图像int OPT
—— MORPH_OPEN / MORPH_CLOSE / MORPH_GRADIENT / MORPH_TOPHAT / MORPH_BLACKHAT 形态学操作类型Mat kernel
—— 结构元素int Iteration
—— 迭代次数,默认是11 原理方法
2 实现步骤
图像形态学操作时,可以通过自定义的结构元素实现结构元素对输入图像一些对象敏感、一些对象不敏感,这样就会让敏感的对象改变而不敏感的对象保留输出。通过使用两个最基本的形态学操作——膨胀和腐蚀,使用不同的结构元素实现对输入图像的操作,得到想要的结果。
imread
cvtColor
adaptiveThreshold
MORPH_OPEN
提取水平垂直线在图像阈值化操作中,更关注在二值化图像中对目标和背景进行分割,但是固定的阈值难以达到理想的分割效果。自适应阈值化操作是根据邻域块的像素值分布确定该像素位置上的二值化阈值。
void adaptiveThreshold(InputArray src, OutputArray dst,
double maxValue, int adaptiveMethod,
int thresholeType, int blockSize, double C)
在Mat对象前加~
可以取反即 像 素 = 255 − 像 素 像素=255-像素 像素=255−像素,和bitwise_not()一样。
#include
#include
using namespace std;
using namespace cv;
int main(int argc, char** argv)
{
Mat src, dst;
src = imread("F:/opencv-4.0.0/etc/Image_collection/bit.png");
if (!src.data)
{
cout << "image load failed!" << endl;
return EOF;
}
namedWindow("origin", WINDOW_AUTOSIZE);
imshow("origin", src);
Mat src_gray;
cvtColor(src, src_gray, COLOR_BGR2GRAY);
Mat temp;
adaptiveThreshold(~src_gray, temp, 255, ADAPTIVE_THRESH_MEAN_C, THRESH_BINARY, 7, 0);
/*
这点要对灰度图像取反,因为我尝试发现如果不取反,灰度图像中的直线如果较粗,
会导致中间部分做自适应阈值后,像素与阈值相等取1,呈现出直线边缘取0(黑),内部取1(白)
*/
Mat kernel = getStructuringElement(MORPH_RECT, Size(src_gray.cols / 32, 1),Point(-1,-1));
morphologyEx(temp, dst, MORPH_OPEN, kernel);
namedWindow("bit", WINDOW_AUTOSIZE);
imshow("bit", ~dst);
waitKey(0);
return 0;
}
#include
#include
using namespace std;
using namespace cv;
int main(int argc, char** argv)
{
Mat src, dst;
src = imread("F:/opencv-4.0.0/etc/Image_collection/ABCD.png");
if (!src.data)
{
cout << "image load failed!" << endl;
return EOF;
}
namedWindow("origin", WINDOW_AUTOSIZE);
imshow("origin", src);
Mat src_gray;
cvtColor(src, src_gray, COLOR_BGR2GRAY);
imshow("origin_gray", src_gray);
Mat temp;
adaptiveThreshold(~src_gray, temp, 255, ADAPTIVE_THRESH_MEAN_C, THRESH_BINARY, 7, 0);
imshow("bit_original", temp);
Mat kernel = getStructuringElement(MORPH_RECT, Size(3, 3));
morphologyEx(temp, dst, MORPH_OPEN, kernel);
blur(~dst, dst, Size(3, 3));
namedWindow("bit", WINDOW_AUTOSIZE);
imshow("bit", dst);
waitKey(0);
return 0;
}
1 图像金字塔概念
2 采样API
可以参考 1、2
分辨率(resolution):采样点的个数
pyrUp(Mat src, Mat dst, Size(src.cols*2, src.rows*2))
生成的图像是原图在宽与高各放大两倍pyrDown(Mat src, Mat dst, Size(src.cols/2, src.rows/2))
生成的图像是原图在宽与高各缩小1/2#include
#include
using namespace std;
using namespace cv;
int main(int argc, char** argv)
{
Mat src, dst;
src = imread("F:/opencv-4.0.0/etc/Image_collection/cat.png");
if (!src.data)
{
cout << "image load failed!" << endl;
return EOF;
}
namedWindow("origin", WINDOW_AUTOSIZE);
imshow("origin", src);
pyrUp(src, dst, Size(src.cols * 2, src.rows * 2);
//pyrDown(src, dst, Size(src.cols / 2, src.rows / 2);
imshow("dst", dst);
//DOG 高斯不同
Mat gray_scale, g1, g2, dog_image; //difference of gaussian
cvtColor(src, gray_scale, CV_BGR2GRAY);
GaussianBlur(src, g1, Size(3, 3), 0, 0);
GaussianBlur(g1, g2, Size(3, 3), 0, 0);
Substract(g1, g2, dog_image);
//归一化显示
normalize(dog_image, dog_image, 255, 0, NORM_MINMAX);
imshow("DOG IMAGE", dog_image);
waitKey(0);
return 0;
}
这里为了使得DOG显示的更明显,引入归一化normalize。
void normalize(InputArray src, OutputArraydst, double alpha = 1, double beta = 0, int norm_type = NORM_L2, int dtype = -1, InputArray mask = noArray() )
1 图像阈值
2 阈值类型
OpenCV官方文档
阈值是什么?简单点说是把图像分割的标尺,这个标尺是根据阈值产生算法(Binary Segmentation二值分割)
这个图片内还包含了自适应阈值化——可以参考第12节
d s t ( x , y ) = { m a x V a l , if src(x,y) > thresh 0 , otherwise dst(x,y)= \begin{cases} maxVal, & \text {if src(x,y) > thresh} \\ 0, & \text{otherwise} \end{cases} dst(x,y)={maxVal,0,if src(x,y) > threshotherwise
大于阈值为1,小于阈值为0
d s t ( x , y ) = { 0 , if src(x,y) > thresh m a x V a l , otherwise dst(x,y)= \begin{cases} 0, & \text {if src(x,y) > thresh} \\ maxVal, & \text{otherwise} \end{cases} dst(x,y)={0,maxVal,if src(x,y) > threshotherwise
大于阈值为0,小于阈值为1
d s t ( x , y ) = { t h r e s h o l d , if src(x,y) > thresh s r c ( x , y ) , otherwise dst(x,y)= \begin{cases} threshold, & \text {if src(x,y) > thresh} \\ src(x,y), & \text{otherwise} \end{cases} dst(x,y)={threshold,src(x,y),if src(x,y) > threshotherwise
大于阈值为阈值,小于阈值保留
d s t ( x , y ) = { s r c ( x , y ) , if src(x,y) > thresh 0 , otherwise dst(x,y)= \begin{cases} src(x,y), & \text {if src(x,y) > thresh} \\ 0, & \text{otherwise} \end{cases} dst(x,y)={src(x,y),0,if src(x,y) > threshotherwise
大于阈值保留,小于阈值取零
d s t ( x , y ) = { 0 , if src(x,y) > thresh s r c ( x , y ) , otherwise dst(x,y)= \begin{cases} 0, & \text {if src(x,y) > thresh} \\ src(x,y), & \text{otherwise} \end{cases} dst(x,y)={0,src(x,y),if src(x,y) > threshotherwise
大于阈值取零,小于阈值保留
适用于大多数求全局阈值情况,但只能针对单一目标分割,对目标和背景大小悬殊,类间方差函数可能呈多峰的情况,效果不好。
原理反推:
OTSU算法是假设了一个阈值,分别对大于阈值的一类C1和小于阈值的一类C2做局部方差,两类的局部均值分别是Mean1和Mean2,图像的全局均值是Mean,像素被分成C1的概率是P1,被分成C2的概率是P2。
P 1 ∗ M e a n 1 + P 2 ∗ M e a n 2 = M e a n P1*Mean1+P2*Mean2=Mean P1∗Mean1+P2∗Mean2=Mean
P 1 + P 2 = 1 P1+P2=1 P1+P2=1
类间方差:
σ 2 = P 1 ( M e a n 1 − M e a n ) 2 + P 2 ( M e a n 2 − M e a n ) 2 \sigma^2=P1(Mean1-Mean)^2+P2(Mean2-Mean)^2 σ2=P1(Mean1−Mean)2+P2(Mean2−Mean)2
σ 2 = P 1 ⋅ P 2 ( M e a n 1 − M e a n 2 ) 2 \sigma^2=P1·P2(Mean1-Mean2)^2 σ2=P1⋅P2(Mean1−Mean2)2
该算法的目的就是使这个类间方差最大化。
可以参考这位博主(写的很详细,我也是借鉴了他的内容):https://blog.csdn.net/weixin_40647819/article/details/90179953
这个不做过多解释了,同样是用来寻找阈值的方法。
这两种算法仅适用于灰度八位图像!!!
#include
#include
using namespace std;
using namespace cv;
Mat src, dst, src_gray;
int thres = 127;
int maxthres = 255;
int type_num = 0;
int type_max = 4;
void Callback(int, void*);
int main(int argc, char** argv)
{
src = imread("F:/opencv-4.0.0/etc/Image_collection/鞠婧祎5.jpg");
if (!src.data)
{
cout << "image load failed!" << endl;
return EOF;
}
namedWindow("origin", WINDOW_AUTOSIZE);
imshow("origin", src);
namedWindow("output", WINDOW_AUTOSIZE);
createTrackbar("Threshold:", "output", &thres, maxthres, Callback);
createTrackbar("Type", "output", &type_num, type_max, Callback);
Callback(0, 0);
waitKey(0);
return 0;
}
void Callback(int, void*)
{
cvtColor(src, src_gray, COLOR_BGR2GRAY);
threshold(src_gray, dst, thres, maxthres, type_num);
imshow("output", dst);
}
1 卷积概念
2 常见算子
3 自定义卷积模糊
我觉得知乎这篇写的很易懂,不是令人头大的傅里叶和拉普拉斯变换https://www.zhihu.com/question/22298352。
Robert_X会使得图像反对角线的边缘差异明显。
原理:像素变化较小的地方,反对角线1和-1使得锚点像素接近0(黑),在像素变化较大的边缘,会使锚点像素>0与周围像素0相比更明显。
∣ + 1 0 0 − 1 ∣ \begin{vmatrix} +1 & 0 \\ 0 & -1 \\ \end{vmatrix} ∣∣∣∣+100−1∣∣∣∣
Robert_Y会使得图像正对角线的边缘差异明显。
∣ 0 + 1 − 1 0 ∣ \begin{vmatrix} 0 & +1 \\ -1 & 0 \\ \end{vmatrix} ∣∣∣∣0−1+10∣∣∣∣
边缘检测,是canny边缘检测重要的一步。
Sobel_X这个x方向的算子会使x方向边缘增强。
∣ − 1 0 1 − 2 0 2 − 1 0 1 ∣ \begin{vmatrix} -1 & 0 & 1 \\ -2 & 0 & 2 \\ -1 & 0 & 1 \\ \end{vmatrix} ∣∣∣∣∣∣−1−2−1000121∣∣∣∣∣∣
Sobel_Y这个y方向的算子会使y方向边缘增强。
∣ − 1 − 2 − 1 0 0 0 1 2 1 ∣ \begin{vmatrix} -1 & -2 & -1 \\ 0 & 0 & 0 \\ 1 & 2 & 1 \\ \end{vmatrix} ∣∣∣∣∣∣−101−202−101∣∣∣∣∣∣
可以获取整个图像的边缘信息。
∣ 0 − 1 0 − 1 4 − 1 0 − 1 0 ∣ \begin{vmatrix} 0 & -1 & 0 \\ -1 & 4 & -1 \\ 0 & -1 & 0 \\ \end{vmatrix} ∣∣∣∣∣∣0−10−14−10−10∣∣∣∣∣∣
使用fliter2D,函数原型(之前详解过入门篇(上)第三节):
void filter2D(InputArray src, OutputArray dst, int ddepth, InputArray kernel, Point anchor = Point(-1, -1), double delta = (0.0), int borderType =4)
应用:
Mat src, dst;
src = imread("PATH.jpg");
Mat kernel = (Mat_<uchar>(2, 2) << 1, 0, 0, -1);
filter2D(src, dst, -1, kernel, Point(-1, -1));
再次对waitKey介绍,可以参考https://www.cnblogs.com/happyamyhope/p/8474850.html
int delay;
int ksize;
int index = 0;
while(true)
{
delay = waitKey(500);
if((char)delay == 27) //ESC
{
break;
}
ksize = 5 + (index % 8) * 2;
Mat kernel = Mat::ones(Size(ksize, ksize), CV_32F) / (float)(ksize * ksize);
filter2D(src, dst, -1, kernel, Point(-1, -1));
index++;
imshow("output", dst);
}
1 卷积边缘问题
2 处理边缘
图像卷积的时候边界像素,不能被卷积操作,原因在于边界像素没有完全跟kernel重叠,所以当3×3滤波时有1个像素的边缘没有被处理,5×5滤波的时候有2个像素的边缘未处理。
在卷积开始之前增加边缘像素,填充的像素值为0或者RGB黑色,比如3×3在四周各填充一个像素的边缘,在深度学习中叫padding,这样就确保图像的边缘被处理,在卷积处理之后再去掉这些边缘。OpenCV中默认的处理方法是:BORDER_DEFAULT,此外常用的还有如下几种:
copyMakeBorder(
Mat src, //输入图像
Mat dst, //添加边缘图像
int top, //边缘长度,一般上下左右都取相同值
int bottom,
int left,
int right,
int borderType, //边缘类型
Scalar value
)
实际上不需要调用copyMakeBorder。调用GaussianBlur时,最后一个参数值默认就是BORDER_DEFAULT,同样也可以换成BORDER_CONSTANT等。
//示例
GaussianBlur(src, dst, Size(5, 5), 0, 0, BORDER_DEFAULT);
1 卷积应用-图像边缘提取
2 相关API
图像求导,由于图像像素值都是离散的,有别于连续的求导方式,使用相邻像素相减即可。
Sobel_X这个x方向的算子会计算x方向边缘梯度。
G x = ∣ − 1 0 1 − 2 0 2 − 1 0 1 ∣ G_x=\begin{vmatrix} -1 & 0 & 1 \\ -2 & 0 & 2 \\ -1 & 0 & 1 \\ \end{vmatrix} Gx=∣∣∣∣∣∣−1−2−1000121∣∣∣∣∣∣
Sobel_Y这个y方向的算子会计算y方向边缘梯度。
G y = ∣ − 1 − 2 − 1 0 0 0 1 2 1 ∣ G_y=\begin{vmatrix} -1 & -2 & -1 \\ 0 & 0 & 0 \\ 1 & 2 & 1 \\ \end{vmatrix} Gy=∣∣∣∣∣∣−101−202−101∣∣∣∣∣∣
最终图像梯度为 G = G x 2 + G y 2 G=\sqrt{G_x^2+G_y^2} G=Gx2+Gy2,为了使得计算机计算更快实际上使用了 G = ∣ G x ∣ + ∣ G y ∣ G=|G_x|+|G_y| G=∣Gx∣+∣Gy∣。
cv::Sobel(
InputArray src, //输入图像
OutputArray dst, //输出图像,大小与输入图像一致
int depth, //输出图像深度
int dx, //x方向,几阶导数
int dy, //y方向,几阶导数
int ksize, //卷积核边长
double scale = 1, //放大倍数
double delta = 0,
int borderType = BORDER_DEFAULT //边缘处理
)
cv::Scharr(
//参数相同
)
使用convertScaleAbs可以实现图像增强的快速运算: d s t i = s a t u r a t e u c h a r ( ∣ α ∗ s r c i + β ∣ ) dst_i=saturate_{uchar}(|\alpha*src_i+\beta|) dsti=saturateuchar(∣α∗srci+β∣)
//使用convertScaleAbs
void cv::convertScaleAbs(
cv::InputArray src, // 输入数组
cv::OutputArray dst, // 输出数组
double alpha = 1.0, // 乘数因子
double beta = 0.0 // 偏移量
);
分别使用cv::Sobel对X方向和Y方向求导,并使用addWeighted将两张图片通过权值合并,得到最终边缘特征提取的图片效果。
1 理论
2 API使用
在二阶微分时,最大变化处的值为零即边缘是零值。通过二阶导数计算,根据此理论可以提取边缘。
L a p l a c e ( f ) = δ 2 f δ x 2 + δ 2 f δ y 2 Laplace(f)=\frac{\delta^2 f}{\delta x^2}+\frac{\delta^2 f}{\delta y^2} Laplace(f)=δx2δ2f+δy2δ2f
处理流程:
//imread就省去了
Mat src, dst, gray_src, edge_image;
GaussianBlur(src, dst, Size(3,3), 0, 0);
cvtColor(dst, gray_src, CV_BGR2GRAY);
//Sobel算子示例,位图深度应≥原深度八位CV_8U
Mat sobel_x, sobel_y;
Sobel(gray_src, sobel_x, -1, 1, 0, 3);
Sobel(gray_src, sobel_y, -1, 0, 1, 3);
addWeighted(sobel_x, 0.5, sobel_y, 0.5, dst);
convertScaleAbs(dst, dst); //先求绝对值,再取saturate
imshow("sobel_image", dst);
//Laplance算子
Mat laplacian_img;
Laplaican(gray_src, laplacian_img, -1, 3);
convertScaleAbs(laplancian_img, laplancian_img);
threshold(laplancian_img, laplancian_img, 0, 255, THRESH_OTSU);
imshow("laplance_img", laplancian_img);
1 Canny算法介绍
2 API cv::Canny()
Canny边缘检测相比Sobel算子,做了改进:
OpenCV中使用Sobel算子求取梯度信息。可以参考上一版。
Sobel_X这个x方向的算子会计算x方向边缘梯度。
G x = ∣ − 1 0 1 − 2 0 2 − 1 0 1 ∣ G_x=\begin{vmatrix} -1 & 0 & 1 \\ -2 & 0 & 2 \\ -1 & 0 & 1 \\ \end{vmatrix} Gx=∣∣∣∣∣∣−1−2−1000121∣∣∣∣∣∣
Sobel_Y这个y方向的算子会计算y方向边缘梯度。
G y = ∣ − 1 − 2 − 1 0 0 0 1 2 1 ∣ G_y=\begin{vmatrix} -1 & -2 & -1 \\ 0 & 0 & 0 \\ 1 & 2 & 1 \\ \end{vmatrix} Gy=∣∣∣∣∣∣−101−202−101∣∣∣∣∣∣
最终图像梯度为: G = G x 2 + G y 2 G=\sqrt{G_x^2+G_y^2} G=Gx2+Gy2
为了使得计算机计算更快实际上使用了: G = ∣ G x ∣ + ∣ G y ∣ G=|G_x|+|G_y| G=∣Gx∣+∣Gy∣角度为: θ M = a r c t a n d y d x \theta_M=arctan\frac{d_y}{d_x} θM=arctandxdy
沿着梯度方向对幅值进行非极大值抑制,而非边缘方向。
抑制方向为:
非最大值抑制就是沿着抑制方向(梯度方向)保留邻域内极大值,删去其他边缘像素值。在每个点上,邻域中心与沿着对应梯度方向的两个像素值相比,若中心像素为最大值,测保留,否则置0,以便细化边缘。
void Canny(InputArray image, OutputArray edges, double threshold1,
double threshold2, int apertureSize=3, bool L2gradient=false)
1 霍夫直线变换介绍
2 API
参考霍夫变换直线检测(Line Detection)原理及示例
cv::HoughLines(
InputArray src, //输入图像,必须是8-bit的灰度图像
OutputArray lines, //输出的极坐标来表示直线
double rho, //生成极坐标时候的像素扫描步长,以像素为单位
double theta, //生成极坐标时候的角度步长,一般取值CV_PI/180,以弧度为单位
int threshold, //阈值。累加计数值的阈值参数,当参数空间某个交点的累加计数的值超过该阈值,则认为该交点对应了图像空间的一条直线。
double srn=0, //默认值为0,用于在多尺度霍夫变换中作为参数rho的除数,rho=rho/srn。
double stn=0, //默认值为0,用于在多尺度霍夫变换中作为参数theta的除数,theta=theta/stn。
//如果srn和stn同时为0,就表示HoughLines函数执行标准霍夫变换,否则就是执行多尺度霍夫变换。
double min_theta=0, //表示角度扫描范围0~180之间
double max_theta=CV_PI
)
cv::HoughLinesP(
InputArray src, //输入图像,必须8-bit的灰度图像
OutputArray lines, //输出的极坐标来表示直线
double rho, //生成极坐标的时候像素扫描步长
double theta, //生成极坐标时的角度步长,一般取值CV_PI/180
int threshold, //阈值,只有获得足够交点的极坐标点才被看成是直线
double minLineLength=0, //最小直线长度
double maxLineGap=0 //最大间隔。表示直线断裂的最大间隔距离阈值。即如果有两条线段是在一条直线上,但它们之间有间隙,那么如果这个间隔距离小于该值,则被认为是一条线段,否则认为是两条线段。
)
#include
#include
#include
using namespace cv;
using namespace std;
int main(int argc, char** argv) {
Mat src, src_gray, dst;
src = imread("F:/opencv-4.0.0/etc/Image_collection/颜文字.jpg");
if (!src.data) {
printf("could not load image...\n");
return -1;
}
char INPUT_TITLE[] = "input image";
char OUTPUT_TITLE[] = "hough-line-detection";
namedWindow(INPUT_TITLE, WINDOW_AUTOSIZE);
namedWindow(OUTPUT_TITLE, WINDOW_AUTOSIZE);
imshow(INPUT_TITLE, src);
// extract edge
Canny(src, src_gray, 150, 200);
cvtColor(src_gray, dst, COLOR_GRAY2BGR);
imshow("edge image", src_gray);
vector<Vec2f> lines;
HoughLines(src_gray, lines, 1, CV_PI / 180, 150, 0, 0);
for (size_t i = 0; i < lines.size(); i++) {
float rho = lines[i][0]; // 极坐标中的r长度
float theta = lines[i][1]; // 极坐标中的角度
Point pt1, pt2;
double a = cos(theta), b = sin(theta);
double x0 = a * rho, y0 = b * rho;
// 转换为平面坐标的四个点
pt1.x = cvRound(x0 + 1000 * (-b));
pt1.y = cvRound(y0 + 1000 * (a));
pt2.x = cvRound(x0 - 1000 * (-b));
pt2.y = cvRound(y0 - 1000 * (a));
line(dst, pt1, pt2, Scalar(0, 0, 255), 1, LINE_AA);
}
/*
vector 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_TITLE, dst);
waitKey(0);
return 0;
}