计算机中的图像是由点阵形式保存的。

通常,边缘检测是检测图像灰度值的不连续性,可以使用Sobel算子检测一阶导数,从而发现边缘。

Sobel算子是3*3矩阵:

C实现Sobel算子边缘检测

分别对原图像进行滤波,分别得到水平和垂直边缘。

对矩阵使用滤波,要用到卷积运算。实现的方法有很多种,例如matlab的imfilter和conv2,opencv的filter2D等,甚至可以它们封装的Sobel进行检测,我在文章C简单实现矩阵相关和卷积中已经实现了一遍,这一次利用自己实现的卷积方法对图像分别进行水平和垂直的边缘检测。

回顾一下两个矩阵的卷积:

与之前实现稍微不同的是,为方便计算,传递的参数模板矩阵w变成了已知的二维数组,类型也变为int型

var **conv2(var **img, int w[3][3], int rows, int cols, int bm=3, int bn=3) {
	int n1 = rows + bm - 1;
	int n2 = cols + bn - 1;
	var **result = (var**)malloc(n1 * sizeof(var*));
	for (int i = 0; i < n1; i++) {
		result[i] = (var*)malloc(n2 * sizeof(var*));
		for (int j = 0; j < n2; j++) {
			int sum = 0;
			for (int m = 0; m < bm; m++) {
				for (int n = 0; n < bn; n++) {
					int rm = i - m;
					int rn = j - n;
					/*补0处理*/
					if (rm >= 0 && rm<rows&&rn >= 0 && rn<cols)
						sum += img[rm][rn] * w[m][n];
				}
			}
			/*取绝对值,超过255则视为255*/
			sum = abs(sum);
			result[i][j] = sum;
			if (sum > 255) result[i][j] = 255;
		}
	}
	return result;
}

我们求边缘,得到的应该是绝对值,这个比较容易理解。但是对于8位灰度图,当数值超过255时就会溢出,因此若超过255,说明此像素点的梯度较大,就设为最大值255。

具体Mat类型和二维数组如何相互转化不赘述,但要注意到转换卷积完成后的图像比原图大模板的维度减1,即对于Sobel来说,原图像的行列分别多了2,可以做一个边界处理:

/*将图像转化为矩阵*/
var **mat2array(Mat &src) {
	int rows = src.rows;
	int cols = src.cols;
	var **result = (var**)malloc(rows * sizeof(var*));

	//double sum = 0;
	for (int i = 0; i < rows; i++) {
		uchar *data = src.ptr<uchar>(i);
		result[i] = (var*)malloc(cols * sizeof(var*));
		for (int j = 0; j < cols ; j++) {
			int temp = data[j];
			result[i][j] = temp;
		}
	}
	return result;
}

/*将矩阵转化为图像*/
Mat array2mat(var **result,int rows,int cols) {
	/*为保持和原图像相同大小*/
	Mat src = Mat(rows-2, cols-2, 0);
	for (int i = 2; i < rows; i++) {
		uchar *data = src.ptr<uchar>(i-2);
		for (int j =2; j < cols; j++) {
			data[j-2]=result[i][j];
		}
	}
	return src;
}

得到水平和垂直图像后,我们需要将两个图像做一个合成,才可以得到最后的边缘图像。

常用的方法是将代表水平和垂直方向的两张图像素值平方的和开根号,即:C实现Sobel算子边缘检测

现在借助opencv的imread和imshow方法用来加载和显示图片,其他与原理无关的方法尽量不用。

以下是完整代码:

#include<stdio.h>
#include<malloc.h>
#include<math.h>
#include<opencv2\opencv.hpp>
using namespace cv;
#define var uchar

var **conv2(var **img, int w[3][3], int rows, int cols, int bm=3, int bn=3) {
	int n1 = rows + bm - 1;
	int n2 = cols + bn - 1;
	var **result = (var**)malloc(n1 * sizeof(var*));
	for (int i = 0; i < n1; i++) {
		result[i] = (var*)malloc(n2 * sizeof(var*));
		for (int j = 0; j < n2; j++) {
			int sum = 0;
			for (int m = 0; m < bm; m++) {
				for (int n = 0; n < bn; n++) {
					int rm = i - m;
					int rn = j - n;
					/*补0处理*/
					if (rm >= 0 && rm<rows&&rn >= 0 && rn<cols)
						sum += img[rm][rn] * w[m][n];
				}
			}
			/*取绝对值,超过255则视为255*/
			sum = abs(sum);
			result[i][j] = sum;
			if (sum > 255) result[i][j] = 255;
		}
	}
	return result;
}

/*将图像转化为矩阵*/
var **mat2array(Mat &src) {
	int rows = src.rows;
	int cols = src.cols;
	var **result = (var**)malloc(rows * sizeof(var*));

	//double sum = 0;
	for (int i = 0; i < rows; i++) {
		uchar *data = src.ptr<uchar>(i);
		result[i] = (var*)malloc(cols * sizeof(var*));
		for (int j = 0; j < cols ; j++) {
			int temp = data[j];
			result[i][j] = temp;
		}
	}
	return result;
}

/*将矩阵转化为图像*/
Mat array2mat(var **result,int rows,int cols) {
	/*为保持和原图像相同大小*/
	Mat src = Mat(rows-2, cols-2, 0);
	for (int i = 2; i < rows; i++) {
		uchar *data = src.ptr<uchar>(i-2);
		for (int j =2; j < cols; j++) {
			data[j-2]=result[i][j];
		}
	}
	return src;
}

Mat addxy(Mat &src1, Mat &src2) {
	int rows = src1.rows > src2.rows ? src1.rows : src2.rows;
	int cols = src1.cols > src2.cols ? src1.cols : src2.cols;
	Mat dst = Mat(rows,cols,0);
	for (int i = 0; i < rows; i++) {
		uchar *data = dst.ptr<uchar>(i);
		uchar *d1 = src1.ptr<uchar>(i);
		uchar *d2 = src2.ptr<uchar>(i);
		for (int j = 0; j < cols; j++) {
			int sum = sqrt(d1[j] * d1[j] + d2[j] * d2[j]);
			if (sum > 255) data[j] = 255;
			else data[j] = sum;
		}
	}
	return dst;
}

int main() {
	/*Sobel算子*/
	int sx[3][3] = {
		{ -1, 0, 1 },
	{ -2, 0, 2 },
	{ -1, 0, 1 },
	};
	int sy[3][3] = {
		{ 1, 2, 1 },
	{ 0, 0, 0 },
	{ -1, -2, -1 },
	};
	/*为简便计算,读入灰度图像*/
	Mat img = imread("D:/Personal/Desktop/ip.png",0);
	/*判断图像是否存在*/
	if (img.rows*img.cols==0) return 1;
	imshow("原图像", img);
	var **array = mat2array(img);
	/*利用sobel算子计算梯度*/
	var **gradx = conv2(array, sx, img.rows, img.cols);
	var **grady = conv2(array, sy, img.rows, img.cols);
	Mat src1 = array2mat(gradx, img.rows, img.cols);
	Mat src2 = array2mat(grady, img.rows, img.cols);
	imshow("水平特征图像", src1);
	imshow("垂直特征图像", src2);
	Mat dst = addxy(src1, src2);
	/*图像叠加*/
	imshow("边缘检测图像", dst);
	for (int i = 0; i < img.rows; i++) {
		free(array[i]);
		free(gradx[i]);
		free(grady[i]);
	}
	free(array);
	free(gradx);
	free(grady);
	waitKey();
}

不久前,苹果发布新一代iPhone,舍友买了一个,那我也皮一下,用iPhoneXS的图片做个边缘检测。

结果如下:

C实现Sobel算子边缘检测

肉眼可以看出,垂直和水平特征图像的区别,边缘检测就是它们叠加后的结果。 

原图来源苹果官网,侵删。如果有错误欢迎指出。

相关文章: