目录ios
首先要明确的一点是图像的卷积/滤波运算,是针对原图像每个像素进行处理,获得一个新的图像的过程。那么进行怎么样的运算呢?要知道图像可以被人所识别,是由于图像中每一个像素并不彻底是离散而独立的,每一个像素都跟周围的像素相关。所以,对每个像素,选定其周围必定范围内的像素值进行运算,获得新的图像的像素值也必定是相关的。而这个范围,就是卷积/滤波的窗口。算法
只有相关的像素值是不够的,还须要改变因子——也就是咱们说的卷积核了。它就是以前说的卷积/滤波的窗口大小,一般由数学原理推导出来的。函数
最后,将窗口内覆盖的像素值和卷积核值相乘并相加,就获得新的像素值填充到新的图像中。对每一个像素值都这样作,就是卷积/滤波运算后新的图像了。spa
以X方向上的一维卷积/滤波为例,选取一个卷积核(-1,0,1),对于图像像素X,其卷积运算的结果Y=-1 × Xa + 0 × X + 1 × Xb,即老是X的后一个像素与前一个像素之差。示意图以下:.
其具体实现代码:.net
#include <iostream> #include <opencv2\opencv.hpp> using namespace cv; using namespace std; int main() { //从文件中读取成灰度图像 const char* imagename = "D:\\Data\\imgDemo\\lena.jpg"; Mat img = imread(imagename, IMREAD_GRAYSCALE); if (img.empty()) { fprintf(stderr, "Can not load image %s\n", imagename); return -1; } //OpenCV函数进行一维卷积(梯度图) Mat xKernel = (Mat_<double>(1, 3) << -1, 0, 1); //卷积算子 Mat Ix; filter2D(img, Ix, -1, xKernel); //自建算法进行一维卷积(梯度图) Mat Ixx; Ixx.create(img.cols, img.rows, CV_8UC1); double xk[3] = { -1, 0, 1 }; //卷积算子 for (int i = 0; i < img.rows; ++i) { for (int j = 0; j < img.cols; ++j) { //img.at<uchar>(i, j) = 255; uchar b[3] = { 0 }; b[0] = (j == 0 ? 0 : img.at<uchar>(i, j - 1)); b[1] = img.at<uchar>(i, j); b[2] = (j == img.cols - 1 ? 0 : img.at<uchar>(i, j + 1)); double value = xk[0] * b[0] + xk[1] * b[1] + xk[2] * b[2]; value = (std::min)(std::max(value, 0.0), 255.0); Ixx.at<uchar>(i, j) = (uchar)value; } } //比较二者的结果 Mat c; compare(Ix, Ixx, c, CMP_EQ); //显示图像 imshow("原始", img); imshow("梯度图(CV)", Ix); imshow("梯度图(MY)", Ixx); imshow("比较结果", c); waitKey(); return 0; }
在这里分别经过OpenCV的filter2D和本身的算法实现了X方向上的一维卷积/滤波运算,显示了其结果图。最后还用compare函数比较二者的差别,纯白(像素值255)色表示二者无差别。其运行结果以下:
code
这里的卷积核(-1,0,1)实际上是从图像的梯度推导出来的。若是把图像看做是函数f(x),那么其X方向上梯度也就是函数X方向上的变化率为:
对全部的像素卷积运算都会除以2,对结果可有可无,所以能够将其舍弃。最后就获得卷积核(-1,0,1)。
除此以外,也能够在Y方向上进行卷积,获得Y方向上的卷积图,只不过卷积核须要转置。blog