上篇博客说到不同图像灰度不同,边界处一般会有明显的边缘,利用此特征可以分割图像。需要说明的是:边缘和物体间的边界并不等同,边缘指的是图像中像素的值有突变的地方,而物体间的边界指的是现实场景中的存在于物体之间的边界。有可能有边缘的地方并非边界,也有可能边界的地方并无边缘,因为现实世界中的物体是三维的,而图像只具有二维信息,从三维到二维的投影成像不可避免的会丢失一部分信息;另外,成像过程中的光照和噪声也是不可避免的重要因素。正是因为这些原因,基于边缘的图像分割仍然是当前图像研究中的世界级难题,目前研究者正在试图在边缘提取中加入高层的语义信息。
常见算子:
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\nmin_val = %f\n", 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\nmin_val = %f\n", 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