一、概述
在OpenGL图片处理中介绍过OpenGL实现边缘检测的方法,本章介绍OpenCV实现边缘检测的方法,这些方法都位于imgproc模块。
二、Sobel算子
Sobel算子的原理是:边缘可以通过定位梯度值大于邻域的相素的方法找到(或者推广到大于一个阀值)。Sobel算子是一个离散微分算子 (discrete differentiation operator),它用来计算图像灰度函数的近似梯度。
Sobel算子结合了高斯平滑和微分求导。当内核大小为 3 时, Sobel内核可能产生比较明显的误差(毕竟,Sobel算子只是求取了导数的近似值)。为解决这一问题,OpenCV提供了Scharr()函数,但该函数仅作用于大小为3的内核。该函数的运算与Sobel函数一样快,但结果却更加精确。
Soble的调用方式如下:
extern "C"
JNIEXPORT jobject JNICALL
Java_com_bc_sample_OpenCVSample_sobel(JNIEnv *env,
jclass thiz, jobject bitmap) {
cv::Mat rgbMat;
bitmapToMat(env, bitmap, rgbMat);
Mat grad, src_gray;
/// 使用高斯滤波消除噪声
GaussianBlur(rgbMat, rgbMat, Size(3, 3), 0, 0, BORDER_DEFAULT);
/// 转换为灰度图
cvtColor(rgbMat, src_gray, CV_RGBA2GRAY);
/// 创建 grad_x 和 grad_y 矩阵
Mat grad_x, grad_y;
Mat abs_grad_x, abs_grad_y;
int scale = 1;
int delta = 0;
int ddepth = CV_16S;
/// 求 X方向梯度
//Scharr( src_gray, grad_x, ddepth, 1, 0, scale, delta, BORDER_DEFAULT );
Sobel(src_gray, grad_x, ddepth, 1, 0, 3, scale, delta, BORDER_DEFAULT);
convertScaleAbs(grad_x, abs_grad_x);
/// 求Y方向梯度
//Scharr( src_gray, grad_y, ddepth, 0, 1, scale, delta, BORDER_DEFAULT );
Sobel(src_gray, grad_y, ddepth, 0, 1, 3, scale, delta, BORDER_DEFAULT);
convertScaleAbs(grad_y, abs_grad_y);
/// 合并梯度(近似)
addWeighted(abs_grad_x, 0.5, abs_grad_y, 0.5, 0, grad);
jobject resultBitmap = createBitmap(env, grad.cols, grad.rows, "ARGB_8888");
matToBitmap(env, grad, resultBitmap);
return resultBitmap;
}
运行后结果如下所示:
三、Laplace算子
Laplace算子的原理是:边缘点的二阶导数为0。所以我们也可以用这个特点来作为检测图像边缘的方法。Laplace算子的调用方式如下:
extern "C"
JNIEXPORT jobject JNICALL
Java_com_bc_sample_OpenCVSample_laplace(JNIEnv *env,
jclass thiz, jobject bitmap) {
cv::Mat rgbMat, src_gray, dst;
bitmapToMat(env, bitmap, rgbMat);
int kernel_size = 3;
int scale = 1;
int delta = 0;
int ddepth = CV_16S;
/// 使用高斯滤波消除噪声
GaussianBlur(rgbMat, rgbMat, Size(3, 3), 0, 0, BORDER_DEFAULT);
/// 转换为灰度图
cvtColor(rgbMat, src_gray, CV_RGB2GRAY);
/// 使用Laplace函数
Mat abs_dst;
Laplacian(src_gray, dst, ddepth, kernel_size, scale, delta, BORDER_DEFAULT);
convertScaleAbs(dst, abs_dst);
jobject resultBitmap = createBitmap(env, abs_dst.cols, abs_dst.rows, "ARGB_8888");
matToBitmap(env, abs_dst, resultBitmap);
return resultBitmap;
}
运行后结果如下所示:
四、Canny算法
Canny边缘检测算法是John F. Canny 于1986年开发出来的一个多级边缘检测算法,也被很多人认为是边缘检测的最优算法。Canny算法的主要步骤是消除噪声、计算梯度幅值和方向、非极大值抑制、滞后阈值。Canny算法的调用步骤如下:
extern "C"
JNIEXPORT jobject JNICALL
Java_com_bc_sample_OpenCVSample_canny(JNIEnv *env,
jclass thiz, jobject bitmap) {
cv::Mat src, src_gray, dst, detected_edges;
bitmapToMat(env, bitmap, src);
/// 转换为灰度图
cvtColor(src, src_gray, CV_RGB2GRAY);
/// 使用高斯滤波消除噪声
GaussianBlur(src, src, Size(3, 3), 0, 0, BORDER_DEFAULT);
int edgeThresh = 1;
int lowThreshold;
int ratio = 3;
int kernel_size = 3;
/// 使用 3x3内核降噪
blur(src_gray, detected_edges, Size(3, 3));
/// 运行Canny算子
Canny(detected_edges, detected_edges, lowThreshold, lowThreshold * ratio, kernel_size);
/// 使用Canny算子输出边缘作为掩码显示原图像
dst = Scalar::all(0);
src.copyTo(dst, detected_edges);
jobject resultBitmap = createBitmap(env, dst.cols, dst.rows, "ARGB_8888");
matToBitmap(env, dst, resultBitmap);
return resultBitmap;
}
运行后结果如下所示:
五、输出轮廓
Opencv的findContours也是一个比较常用的方法,这个方法可以把图片的轮廓输出到一个vector中。常用来和以上边缘检测算法配合使用来得到一个比较理想的轮廓结果。
extern "C"
std::vector<std::vector<Point>> opencv_sample_findContours(JNIEnv *env,
jclass thiz, jobject bitmap) {
cv::Mat src;
bitmapToMat(env, bitmap, src);
std::vector<std::vector<Point>> contours;
std::vector<Vec4i> hierarchy;
/// 寻找轮廓,或者也可以在canny算法后的mat上使用寻找轮廓来获得比较理想的结果
findContours(src, contours, hierarchy, CV_RETR_TREE, CV_CHAIN_APPROX_SIMPLE, Point(0, 0) );
return contours;
}
The End
欢迎关注我,一起解锁更多技能:BC的掘金主页~💐 BC的CSDN主页~💐💐
OpenCV官网:https://opencv.org/releases/
OpenCV github:https://github.com/opencv/opencv/tree/master
OpenCV2.3.2相关文档:https://www.w3cschool.cn/opencv/
BC的OpenCV专栏:https://juejin.cn/column/7082319523646291976
LearnOpenCV学习资料
OpenCV 4.5.5官方文档
OpenCV 2.3.2官方文档