边缘处理
卷积边缘问题
图像卷积的时候边界像素,不能被卷积操作,原因在于边界像素没有完全跟kernel重叠,所以当3X3滤波时候有1个像素的边缘没有被处理,5X5滤波的时候有2个像素的边缘没有被处理。
处理边缘
在卷积开始之前增加边缘像素,填充的像素值为0或者RGB黑色,比如3x3在四周个填充1个像素的边缘,这样就确保图像的边缘被处理,在卷积处理之后再去掉这些边缘,openCV中默认的处理方法是:BORDER_DEFAULT,此外常用的还有如下几种:
BORDER_CONSTANT---填充边缘用指定像素值
BORDER_REPLICATE---填充边缘像素用已知的边缘像素值
BORDER_WRAP---用另外一边的像素来补偿填充
API说明---给图像添加边缘API
copyMakeBorder(
Mat src,//输入图像
Mat dst,//添加边缘图像
int top,//边缘长度,一般上下左右都去相同值
int bottom,
int left,
int right,
int borderType,//边缘类型
Scalar value
)
代码演示:
#include<opencv2/opencv.hpp>
#include<iostream>
#include<math.h>
using namespace cv;
int main(int argc, char** argv) {
Mat src, dst;
src = imread("C:\\Users\\Lenovo\\Desktop\\毕业设计\\AAAAAAA\\1.jpg");
if (!src.data) {
printf("could not load image...\n");
return -1;
}
char INPUT_WIN[] = "input image";
char OUTPUT_WIN[] = "Border image";
namedWindow(INPUT_WIN, CV_WINDOW_AUTOSIZE);
namedWindow(OUTPUT_WIN,CV_WINDOW_AUTOSIZE);
imshow(INPUT_WIN, dst);
int top = (int)(0.05 * src.rows);
int left = (int)(0.05 * src.cols);
int right = (int)(0.05 * src.cols);
int bottom =(int)(0.05 * src.rows);
RNG rng(12345);
int borderType = BORDER_DEFAULT;
int c = 0;
while (true) {
c = waitKey(500);
if ((char)c == 27) {//ESC退出
break;
}
if ((char)c == 'r') {
borderType = BORDER_REPLICATE;
}
else if ((char)c == 'w') {
borderType = BORDER_WRAP;
}
else if ((char)c == 'c') {
borderType = BORDER_CONSTANT;
}
else {
borderType = BORDER_DEFAULT;
}
Scalar color = Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255));
copyMakeBorder(src, dst, top, bottom, left, right, borderType, color);
imshow(OUTPUT_WIN, dst);
}
waitKey(0);
return 0;
}
Sobel算子
卷积应用---图像边缘提取
边缘是什么--是像素值发生跃迁的地方,是图像显著特征之一,在图像特征提取、对象检测、模式识别等方面都有重要作用
如何捕捉/提取边缘---对图像求它的一阶导数
delta=f(x) - f(x-1) ,delta越大,说明像素在x方向变化越大,边缘信号越强。
Sobel算子
是离散微分算子(discrete differentiation operator),用来计算图像灰度的近似梯度
Sobel算子功能集合高斯平滑和微分求导
又被称为一阶微分算子,求导算子,在水平和垂直两个方向上求导,得到图像X方向与Y方向梯度图像
拉普拉斯算子是二阶的求导算子
水平梯度 垂直梯度
Gx = -1 0 1 Gy = -1 -2 -1
-2 0 2 0 0 0
-1 0 1 1 2 1
根据权重扩大差异
最终图像梯度
G = (Gx * Gx + Gy * Gy)的开平方
实际上采用的近似算法: G = |Gx| + |Gy|
Sobel算子
求取倒数的近似值,kernel=3时不是很准确,使用改进版本的Scharr函数,算子如下:
-3 0 3 -3 -10 -3
Gx =-10 0 10 Gy = 0 0 0
-3 0 3 3 10 3
API说明cv::Sobel
cv::Sobel(
InputArray src,//输入图像
OutputArray dst,//输出图像,大小与输入图像一致
int depth,//输出图像深度
int dx,//x方向,几阶导数
int dy,//y方向,几阶导数
int ksize,//Sobel算子kernel大小,必须是1,3,5,7
double scale = 1,
double delta = 0,
int borderType = BORDER_DEFAULT
)
Input depth() Output depth(ddepth)
CV_8U -1/CV_16S/CV_32F/CV_64F
CV_16U/CV_16S -1/CV_32F/CV_64F
CV_32F -1/CV_32F/CV_64F
CV_64F -1/CV_64F
OpenCV改进版本的Scharr
API说明cv::Scharr
cv::Scharr(
InputArray src, //输入图像
OutputArray dst, //输出图像,大小与输入图像一致
int depth, //输出图像深度
int dx, //x方向,几阶导数
int dy, //Y方向,几阶导数
double scale = 1,
double delta = 0,
int borderType = BORDER_DEFAULT
)
缺点:
对噪声敏感,容易受到影响
高斯模糊降噪,平滑
变成灰度图像
求梯度 x 和 y
混合 x y
振幅图像
代码演示:
#include<opencv2/opencv.hpp>
#include<iostream>
#include<math.h>
using namespace cv;
int main(int argc,char** argv){
Mat src,dst;
src = imread("");
if(!src.data){
printf("could not load image...\n");
return -1;
}
char INPUT_WIN[] = "Input image";
char OUTPUT_WIN[] = "Output image";
namedWindow(INPUT_WIN,CV_WINDOW_AUTOSIZE);
imshow(INPUT_WIN,src);
Mat gray_src;
GaussianBlur(src,dst,Size(3,3),0,0);
cvtColor(dst,gray_src,CV_BGR2GRAY);
imshow("gray image",gray_src);
Mat xgrad,ygrad;
//原始版本
//Sobel(gray_src,xgrad,CV_16S,1,0,3);
//Sobel(gray_src,ygrad,CV_16S,0,1,3);
//改进版本
Scharr(gray_src,xgrad,CV_16S,1,0);
Scharr(gray_src,ygrad,CV_16S,0,1);
convertScaleAbs(xgrad,xgrad);
convertScaleAbs(ygrad,ygrad);
imshow("xgrad image",xgrad);
imshow("ygrad image",ygrad);
//第一种算法
//Mat xygrad;
//addWeighted(xgrad,0.5,ygrad,0.5,0.xygrad);
//imshow("Final image",xygrad);
//第二种算法,效果更好
Mat xygrad = Mat(xgrad.size(),xgrad.type());
int width = xgrad.cols;
int height = xgrad.rows;
for(int row = 0; row < height; row++){
for(int col = 0; col < width; col++){
int xg = xgrad.at<uchar>(row,col);
int yg = ygrad.at<uchar>(row,col);
int xy = xg + yg;
xygrad.at<uchar>(row,col) = saturate_cast<uchar>(xy);
}
}
waitKey(0);
return 0;
}
Laplance算子
理论
解释:在二阶导数的时候,最大变化的值为零,即边缘是零值。通过二阶导数计算,依据此理论我们可以计算图像二阶导数,提取边缘。
Laplance算子:
Laplance(f) = f(x)的二阶导 + f(y)的二阶导
相关API cv::Laplance
处理流程:
高斯模糊---去噪声(GaussianBlur())
转换为灰度图像cvtColor()
拉普拉斯---二阶导数计算(laplacian())
取绝对值converScaleAbs()
显示结果
#include<opencv2/opencv.hpp>
#include<iostream>
#include<math.h>
using namespace cv;
int main(int argc,char** argv){
Mat src,dst;
src = imread();
if(!src.data){
printf("could not load image\n");
return -1;
}
namedWindow("input image",CV_WINDOW_AUTOSIZE);
imshow("input image",src);
Mat gray_src,edg_image;
GaussianBlur(src,dst,Size(3,3),0,0);
cvtColor(dst,gray_src,CV_BGR2GRAY);
Laplacian(gray_src,edg_image,CV_16S,3);
convertScaleAbs(edg_image,edg_image);
//图像二值化
threshold(edg_image,edg_image,0,255,THRESH_OTSU|THRESH_BINARY);
namedWindow("output image",CV_WINDOW_AUTOSIZE);
imshow("output image",edg_image);
waitKey(0);
return 0;
}
Canny边缘检测
Canny算法介绍
Canny是边缘检测算法,在1986年提出的
是一个很好的边缘提取算法
很常用也很实用的图像处理方法
步骤:cv::Canny
高斯模糊---GaussianBlur
灰度转换---cvtColor
计算梯度---Sobel/Scharr
非最大信号抑制
高低阈值输出二值图像
非最大信号抑制:
Gx = -1 0 1 Gy = -1 -2 -1
-2 0 2 0 0 0
-1 0 1 1 2 1
G = Gx * Gx + Gy * Gy 的平方根
角度 = arctan (Gx/Gy)
其中黄色区域的取值范围为0~22.5与157.5~180
绿色区域取值范围22.5~67.5
蓝色区域取值范围67.5~112.5
红色区域取值范围112.5~157.5
高低阈值输出二值图像
T1,T2为阈值,凡是高于T2的都保留,凡是小于T1都丢弃,从高于T2的像素出发,凡是大于T1而且相互连接的都保留。最终得到一个输出二值图像
推荐的高低阈值比值为T2:T1 = 3;1/2:1其中T2为高阈值,T1为低阈值
APIcv::Canny()
Canny(
InputArray src,//8-bit的输入图像
OutputArray edges,//输出边缘图像,一般都是二值图像,背景是黑色
double threshold1,//低阈值,常取高阈值的1/2或者1/3
double threshold2,//高阈值
int aptertureSize,//Sobel算子的size,通常3x3取值3
bool L2gradient//选择true表示L2来归一化,否则用L1归一化
)
#include<opencv2/opencv.hpp>
#include<iostream>
#include<math.h>
using namespace cv;
Mat src,dst,gray_src;
int t1_value = 50;
int max_value = 255;
void Canny_Demo(int,void*);
int main(int argc,char** argv){
src = imread();
if(!src.data){
printf("could not load image\n");
return -1;
}
namedWindow("input image",CV_WINDOW_AUTOSIZE);
imshow("input image",src);
cvtColor(src,gray_src,CV_BGR2GRAY);
createTrackbar("Threshold Value","output image",&t1_value,max_value,Canny_Demo);
Canny_Demo(0,0);
waitKey(0);
return 0;
}
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 image",dst);
}