上篇博客说到不同图像灰度不同,边界处一般会有明显的边缘,利用此特征可以分割图像。需要说明的是:边缘和物体间的边界并不等同,边缘指的是图像中像素的值有突变的地方,而物体间的边界指的是现实场景中的存在于物体之间的边界。有可能有边缘的地方并非边界,也有可能边界的地方并无边缘,因为现实世界中的物体是三维的,而图像只具有二维信息,从三维到二维的投影成像不可避免的会丢失一部分信息;另外,成像过程中的光照和噪声也是不可避免的重要因素。正是因为这些原因,基于边缘的图像分割仍然是当前图像研究中的世界级难题,目前研究者正在试图在边缘提取中加入高层的语义信息。
那么如何表示边缘检测?
在数学上,用导数来表示改变的快慢。基于此,有许多方法用于边缘检测,他们绝大部分可以划分为两类:基于查找的一类和基于零穿越的一类。基于查找的方法通过寻找图像一阶导数中的最大值和最小值来检测边界,通常将边界定位在梯度最大的方向(想想一阶导数的含义是图像变化的速度,最大的自然就是变化最显著的)。基于零穿越的方法通过寻找图像二阶导数零穿越来寻找边界,通常是Laplacian过零点或者非线性差分表示的过零点。
图像在数学上可看做是二维离散函数,图像梯度就是这个二维离散函数的求导。函数f(x,y)在(x,y)处的梯度为一个向量:
常见算子:
vs2015实现一下:
#include<opencv2/core/core.hpp> #include<opencv2/highgui/highgui.hpp> #include<cv.h> using namespace cv; //Robert模板锐化图像 IplImage *Robert_cx(IplImage *src) { float a[] = { -1,0, 0,1 }; CvMat kernel = cvMat(2, 2, CV_32F, a); IplImage *dst = cvCreateImage(cvGetSize(src), IPL_DEPTH_8U, src->nChannels); cvFilter2D(src, dst, &kernel); return dst; } int main() { IplImage * srcImage = cvLoadImage("E:\VS2015Opencv\vs2015\project\picture\01.jpg", 1); //窗口定义函数 cvNamedWindow("原彩色图像图像", CV_WINDOW_AUTOSIZE); //显示源图像 cvShowImage("原彩色图像图像", srcImage); //显示Robert变换后的彩色图像 cvNamedWindow("Robert变换后的彩色图像", CV_WINDOW_AUTOSIZE); cvShowImage("Robert变换后的彩色图像", Robert_cx(srcImage));// 等待60000 ms后窗口自动关闭 waitKey(60000); }
一种很直观的实现代码,根据公式推导实现
#include <opencv2/opencv.hpp> #include <opencv2/imgproc/imgproc.hpp> #include <iostream> using namespace std; using namespace cv; void Roberts(Mat &src, Mat &dst); //函数声明 /*定义Roberts算子函数*/ void Roberts(Mat &src, Mat &dst) { dst = src.clone(); int nWidth = dst.cols; //列数 int nHeight = dst.rows; //行数 uchar pixel[4]; //定义数组用于保存两行4个像素的值 for (int j = 0; j<nHeight - 1; j++) //行 { //同时处理两行像素 uchar* Lup = dst.ptr<uchar>(j); //上一行遍历 uchar *Ldown = dst.ptr<uchar>(j + 1); //下一行遍历 for (int i = 0; i<nWidth - 1; i++) { //生成Roberts算子 pixel[0] = Lup[i]; pixel[1] = Lup[i + 1]; pixel[2] = Ldown[i]; pixel[3] = Ldown[i + 1]; //模板实现,及公式; Lup[i] = sqrt(double((pixel[0] - pixel[3]) * (pixel[0] - pixel[3]) + (pixel[1] - pixel[2]) * (pixel[1] - pixel[2]))); } } } int main() { Mat srcImage = imread("E:\VS2015Opencv\vs2015\project\picture\01.jpg", 0); cout << srcImage.channels() << endl; namedWindow("原图"); moveWindow("原图", 60, 60); imshow("原图", srcImage); Mat dst(srcImage.size(), srcImage.type()); Roberts(srcImage, dst); //调用Roberts函数 imwrite("dst.jpg", dst); namedWindow("效果图"); moveWindow("效果图", 320, 320); imshow("效果图", dst); waitKey(0); return 0; }
#include "opencv2/highgui/highgui.hpp" #include "opencv2/imgproc/imgproc.hpp" using namespace cv; //prewitt算子,模板卷积公式编写,常用方法 void prewitt(IplImage *src, IplImage *dst) { //定义prewitt算子的模板 float prewittx[9] = { -1,0,1, -1,0,1, -1,0,1 }; float prewitty[9] = { 1,1,1, 0,0,0, -1,-1,-1 }; CvMat px; px = cvMat(3, 3, CV_32F, prewittx); CvMat py; py = cvMat(3, 3, CV_32F, prewitty); //为输出图像申请空间 IplImage *dstx = cvCreateImage(cvGetSize(src), 8, 1); IplImage *dsty = cvCreateImage(cvGetSize(src), 8, 1); //对图像使用模板,自动填充边界 cvFilter2D(src, dstx, &px, cvPoint(-1, -1)); cvFilter2D(src, dsty, &py, cvPoint(-1, -1)); //计算梯度,范数为2,注意学习指针的使用方法 int i, j, temp; float tempx, tempy; //定义为浮点型是为了避免sqrt函数引起歧义 uchar* ptrx = (uchar*)dstx->imageData; uchar* ptry = (uchar*)dsty->imageData; for (i = 0; i<src->width; i++) { for (j = 0; j<src->height; j++) { tempx = ptrx[i + j*dstx->widthStep]; //tempx,tempy表示的是指针所指向的像素 tempy = ptry[i + j*dsty->widthStep]; temp = (int)sqrt(tempx*tempx + tempy*tempy); /*if(temp>100) temp = 255; else temp = 0;*/ dst->imageData[i + j*dstx->widthStep] = temp; } } double min_val = 0, max_val = 0;//取图并显示像中的最大最小像素值 cvMinMaxLoc(dst, &min_val, &max_val); printf("max_val = %f min_val = %f ", max_val, min_val); //计算梯度,范数为1 //cvAdd(dstx,dsty,dst); //cvSaveImage("PrewittImg.jpg", dst);//把图像存入文件 cvReleaseImage(&dstx); cvReleaseImage(&dsty); cvNamedWindow("prewitt", 1); cvShowImage("prewitt", dst); cvWaitKey(0); } void main() { IplImage *src, *dst; src = cvLoadImage("E:\VS2015Opencv\vs2015\project\picture\01.jpg", 0); dst = cvCreateImage(cvGetSize(src), IPL_DEPTH_8U, 1); cvNamedWindow("src", 1); cvShowImage("src", src); //cvSmooth(src,src,CV_MEDIAN,3,3,0,0); //cvThreshold(src,src,95,255,CV_THRESH_BINARY); prewitt(src,dst); }
简易代码实现:
#include "opencv2/highgui/highgui.hpp" #include "opencv2/imgproc/imgproc.hpp" using namespace cv; //prewitt算子,模板卷积公式编写,常用方法 void sobel(IplImage *src, IplImage *dst) { //为soble微分图像申请空间,创建图片函数 IplImage *pSobelImg_dx = cvCreateImage(cvGetSize(src), 32, 1); IplImage *pSobelImg_dy = cvCreateImage(cvGetSize(src), 32, 1); IplImage *pSobelImg_dxdy = cvCreateImage(cvGetSize(src), 32, 1); //用sobel算子计算两个方向的微分 cvSobel(src, pSobelImg_dx, 1, 0, 3); cvSobel(src, pSobelImg_dy, 0, 1, 3); //total gradient = sqrt(horizontal*horizontal+vertical*vertical) int i, j; double v1, v2, v; for (i = 0; i < src->height; i++) { for (j = 0; j < src->width; j++) { v1 = cvGetReal2D(pSobelImg_dx, i, j); v2 = cvGetReal2D(pSobelImg_dy, i, j); v = sqrt(v1*v1 + v2*v2); /* if(v>100) v = 255; else v = 0;*/ cvSetReal2D(pSobelImg_dxdy, i, j, v); } } cvConvertScale(pSobelImg_dxdy, dst); //将图像转化为8位 double min_val = 0, max_val = 0;//取图并显示像中的最大最小像素值 cvMinMaxLoc(pSobelImg_dxdy, &min_val, &max_val); printf("max_val = %f min_val = %f ", max_val, min_val); //归一化 cvNormalize(dst, dst, 0, 255, CV_MINMAX, 0); cvNamedWindow("sobel", 1); cvShowImage("sobel", dst); cvWaitKey(0); } void main() { IplImage *src, *dst; src = cvLoadImage("E:\VS2015Opencv\vs2015\project\picture\01.jpg", 0); dst = cvCreateImage(cvGetSize(src), IPL_DEPTH_8U, 1); cvNamedWindow("src", 1); cvShowImage("src", src); //cvSmooth(src,src,CV_MEDIAN,3,3,0,0); //cvThreshold(src,src,95,255,CV_THRESH_BINARY); sobel(src, dst); }
上面代码中用到opencv中sobel()函数;
实现代码:
#include "core/core.hpp" #include "highgui/highgui.hpp" #include "imgproc/imgproc.hpp" #include "iostream" using namespace std; using namespace cv; int main(int argc, char *argv[]) { Mat image = imread("E:\VS2015Opencv\vs2015\project\picture\01.jpg", 0); Mat imageX = Mat::zeros(image.size(), CV_16SC1); Mat imageY = Mat::zeros(image.size(), CV_16SC1); Mat imageXY = Mat::zeros(image.size(), CV_16SC1); Mat imageX8UC; Mat imageY8UC; Mat imageXY8UC; if (!image.data) { return -1; } GaussianBlur(image, image, Size(3, 3), 0); //高斯滤波消除噪点 uchar *P = image.data; uchar *PX = imageX.data; uchar *PY = imageY.data; int step = image.step; int stepXY = imageX.step; for (int i = 1; i<image.rows - 1; i++) { for (int j = 1; j<image.cols - 1; j++) { //通过指针遍历图像上每一个像素 PX[i*imageX.step + j*(stepXY / step)] = abs(P[(i - 1)*step + j + 1] + P[i*step + j + 1] * 2 + P[(i + 1)*step + j + 1] - P[(i - 1)*step + j - 1] - P[i*step + j - 1] * 2 - P[(i + 1)*step + j - 1]); PY[i*imageX.step + j*(stepXY / step)] = abs(P[(i + 1)*step + j - 1] + P[(i + 1)*step + j] * 2 + P[(i + 1)*step + j + 1] - P[(i - 1)*step + j - 1] - P[(i - 1)*step + j] * 2 - P[(i - 1)*step + j + 1]); } } addWeighted(imageX, 0.5, imageY, 0.5, 0, imageXY);//融合X、Y方向 convertScaleAbs(imageX, imageX8UC); convertScaleAbs(imageY, imageY8UC); convertScaleAbs(imageXY, imageXY8UC); //转换为8bit图像 Mat imageSobel; Sobel(image, imageSobel, CV_8UC1, 1, 1); //Opencv的Sobel函数 imshow("Source Image", image); imshow("X Direction", imageX8UC); imshow("Y Direction", imageY8UC); imshow("XY Direction", imageXY8UC); imshow("Opencv Soble", imageSobel); waitKey(); return 0; }
下面这个结果的代码
#include <opencv2/opencv.hpp> #include <iostream> using namespace cv; using namespace std; int main(int argc, char** argv) { Mat src = imread("E:\VS2015Opencv\vs2015\project\picture\01.jpg"); Mat dst, gray, grad_x, gray_y, abs_grad_x, abs_grad_y; //转成灰度图 cvtColor(src, gray, COLOR_BGR2GRAY); //均值滤波降噪,也可以用其他滤波方法 blur(gray, src, Size(3, 3)); //运行Sobel算子,得到边缘 //求x方向梯度 Sobel(src, grad_x, CV_16S, 1, 0, 3); convertScaleAbs(grad_x, abs_grad_x);//提取的深度图片进行显示时,由于是16位图片,要将图片转化成为8位图形进行显示 imshow("x方向的sobel", abs_grad_x); //运行Sobel算子,得到边缘 //求y方向梯度 Sobel(src, gray_y, CV_16S, 0, 1, 3); convertScaleAbs(gray_y, abs_grad_y); imshow("y方向的sobel", abs_grad_y); //合并梯度 addWeighted(abs_grad_x, 0.5, abs_grad_y, 0.5, 0, dst); imshow("合成的整体效果图", dst); waitKey(0); return 0; }
sobel实现可参考:http://blog.sina.com.cn/s/blog_6ef955fa0102v2ba.html
Sobel边缘检测的C++实现:https://github.com/RonnyYoung/ImageFeatures/blob/master/source/sobel.cpp
Canny边缘检测的实现
https://github.com/RonnyYoung/ImageFeatures/blob/master/source/canny.cpp
也可参考我的前面博客;https://www.cnblogs.com/fcfc940503/p/11282329.html
本文参考思维之际:https://www.cnblogs.com/ronny/p/4001910.html