1、Canny边缘检测
使用OpenCV和CUDA实现Canny边缘检测算法,该算法结合了高斯滤波、梯度寻找、非最大抑制和滞后阈值处理。如上一章所述,高通滤波器对噪声非常敏感,在Canny边缘检测中,检测边缘之前完成高斯平滑,这使得它对噪声不太敏感。在检测到边缘以从结果中移除不必要的边缘之后,它还具有非最大抑制阶段。
Canny边缘检测是一项计算密集型任务,难以在实时应用程序中使用,CUDA版本的算法可起到加速作用。实现Canny边缘检测算法的代码如下所述:
#include <cmath> #include <iostream> #include "opencv2/opencv.hpp" using namespace std; using namespace cv; using namespace cv::cuda; int main() { Mat h_image = imread("images/drawing.JPG",0); if (h_image.empty()) { cout << "can not open image"<< endl; return -1; } GpuMat d_edge,d_image; Mat h_edge; d_image.upload(h_image); cv::Ptr<cv::cuda::CannyEdgeDetector> canny_edge = cv::cuda::createCannyEdgeDetector(2.0, 100.0, 3, false); canny_edge->detect(d_image, d_edge); d_edge.download(h_edge); imshow("source", h_image); imshow("detected edges", h_edge); waitKey(0); return 0; }
OpenCV和CUDA为Canny边缘检测提供了createCannyEdgeDetector类。创建此类的对象时可以传递许多参数:第一个和第二个参数是滞后阈值的低阈值和高阈值,如果某点的强度梯度大于高阈值,则将其归类为边缘点;如果梯度小于低阈值,则该点不是边缘点;如果梯度在两个阈值之间,则基于连接性来确定该点是否为边缘点。第三个参数是边缘检测器的孔径大小,最后一个参数是布尔参数,它指示是否使用L2_norm或L1_norm进行梯度幅度计算。L2_norm很消耗计算资源但更准确,true值表示使用L2_norm。代码输出如图:
2、使用Hough变换进行直线检测
直线的检测在许多计算机视觉应用中很重要,例如车道检测。它还可用于检测属于其他常规形状的线条。Hough变换是一种流行的特征提取技术,用于计算机视觉直线检测。我们不会详细讨论Hough变换如何检测直线,但我们将了解如何在OpenCV和CUDA中实现。实现直线检测的Hough变换代码如下:
#include <cmath> #include <iostream> #include "opencv2/opencv.hpp" using namespace std; using namespace cv; using namespace cv::cuda; int main() { Mat h_image = imread("images/drawing.JPG",0); if (h_image.empty()) { cout << "can not open image"<< endl; return -1; } Mat h_edge; cv::Canny(h_image, h_edge, 100, 200, 3); Mat h_imagec; cv::cvtColor(h_edge, h_imagec, COLOR_GRAY2BGR); Mat h_imageg = h_imagec.clone(); vector<Vec4i> h_lines; { const int64 start = getTickCount(); HoughLinesP(h_edge, h_lines, 1, CV_PI / 180, 50, 60, 5); const double time_elapsed = (getTickCount() - start) / getTickFrequency(); cout << "CPU Time : " << time_elapsed * 1000 << " ms" << endl; cout << "CPU FPS : " << (1/time_elapsed) << endl; } for (size_t i = 0; i < h_lines.size(); ++i) { Vec4i line_point = h_lines[i]; line(h_imagec, Point(line_point[0], line_point[1]), Point(line_point[2], line_point[3]), Scalar(0, 0, 255), 2, LINE_AA); } GpuMat d_edge, d_lines; d_edge.upload(h_edge); { const int64 start = getTickCount(); Ptr<cuda::HoughSegmentDetector> hough = cuda::createHoughSegmentDetector(1.0f, (float) (CV_PI / 180.0f), 50, 5); hough->detect(d_edge, d_lines); const double time_elapsed = (getTickCount() - start) / getTickFrequency(); cout << "GPU Time : " << time_elapsed * 1000 << " ms" << endl; cout << "GPU FPS : " << (1/time_elapsed) << endl; } vector<Vec4i> lines_g; if (!d_lines.empty()) { lines_g.resize(d_lines.cols); Mat h_lines(1, d_lines.cols, CV_32SC4, &lines_g[0]); d_lines.download(h_lines); } for (size_t i = 0; i < lines_g.size(); ++i) { Vec4i line_point = lines_g[i]; line(h_imageg, Point(line_point[0], line_point[1]), Point(line_point[2], line_point[3]), Scalar(0, 0, 255), 2, LINE_AA); } imshow("source", h_image); imshow("detected lines [CPU]", h_imagec); imshow("detected lines [GPU]", h_imageg); imwrite("hough_source.png", h_image); imwrite("hough_cpu_line.png", h_imagec); imwrite("hough_gpu_line.png", h_imageg); waitKey(0); return 0; }
OpenCV提供了createHoughSegmentDetector类来实现Hough变换。它需要图像的边缘图作为输入。因此边缘可以被Canny边缘检测器从图像中检测。Canny边缘检测器的输出上传到用于GPU计算的设备显存。上一节中已讨论过如何用GPU去执行边缘计算。
当创建createHoughSegmentDetector对象时,需要很多参数:第一个参数r表示在Hough变换中参数的分辨率,通常作为1像素;第二个参数是参数theta在弧度中的分辨率,取1弧度或pi/180;第三个参数是最小数量形成一条线所需的点数,取50像素;最后的参数是两点之间的最大间隙被视为同一条线,这里设为5个像素。
创建的对象检测方法用于检测直线,需要两个参数:第一个参数是要检测边缘的图像,第二个参数是存储检测到的线上点的数组。数组包含检测到的线的起始点和结束点,这个数组使用for循环迭代,使用OpenCV的线函数在图像上绘制单独线条,最终图像使用imshow函数显示。
Hough变换是一个数学密集型的步骤,为了展示CUDA的优势,我们用CPU实现相同的算法,并将其性能与CUDA代码进行比较。
HoughLinesP函数使用概率Hough变换检测CPU上的线路转变,前两个参数是源图像和存储输出线上点的数组;第三和第四个参数是r与theta的分辨率;第五个参数是表示线的最小交叉点数的阈值;第六个参数表示形成一条线所需的最小点数;最后的参数表示在同一条线上要考虑的点之间的最大间隙。
使用for循环对函数返回的数组进行迭代,以在原始图像上显示检测到的线。下图显示GPU与CPU功能的输出结果:
3、对圆形进行检测
Hough变换也可用于圆形检测,可以用在如球检测和跟踪以及硬币检测等这些圆形对象的很多应用中。OpenCV和CUDA提供了实现这个检测的类,Hough变换用于硬币检测的代码如下:
#include "opencv2/opencv.hpp" #include <iostream> using namespace cv; using namespace std; int main(int argc, char** argv) { Mat h_image = imread("images/eight.tif", IMREAD_COLOR); Mat h_gray; cvtColor(h_image, h_gray, COLOR_BGR2GRAY); cuda::GpuMat d_gray,d_result; std::vector<cv::Vec3f> d_Circles; medianBlur(h_gray, h_gray, 5); cv::Ptr<cv::cuda::HoughCirclesDetector> detector = cv::cuda::createHoughCirclesDetector(1, 100, 122, 50, 1, max(h_image.size().width, h_image.size().height)); d_gray.upload(h_gray); detector->detect(d_gray, d_result); d_Circles.resize(d_result.size().width); if (!d_Circles.empty()) d_result.row(0).download(cv::Mat(d_Circles).reshape(3, 1)); cout<<"No of circles: " <<d_Circles.size() <<endl; for( size_t i = 0; i < d_Circles.size(); i++ ) { Vec3i cir = d_Circles[i]; circle( h_image, Point(cir[0], cir[1]), cir[2], Scalar(255,0,0), 2, LINE_AA); } imshow("detected circles", h_image); waitKey(0); return 0; }
这里的createHoughCirclesDetector类用于检测循环对象,创建该对象时需提供许多参数:第一个参数是dp,表示累加器分辨率与图像分辨率的反比,取值通常为1;第二个参数是检测到的圆中心之间的最小距离;第三个参数是Canny阈值;第四个参数是累加器阈值;第五和第六个参数是要检测的圆的最小和最大半径。
圆中心之间的最小距离取为100像素,你可以在这个值上下调整:如果调低,在原始图像上就会错误地检测到许多圆;如果增加,可能会遗漏一些真正的圆。最后两个参数,即最小和最大半径,如果不确定尺寸的话,可以取0。在上面的代码中,它被取为1和最大维度图像的一部分,用于检测图像中的所有圆形。这段代码的输出结果如图所示
Hough变换对高斯噪声和椒盐噪声非常敏感,所以有时应用Hough变换之前最好使用高斯滤波器和中值滤波器对图像进行预处理转变,这样会得到更准确的结果。