这里我们先说图像腐蚀
图像腐蚀的原理是缩小腐蚀中心的像素值,从而达到降低图片高亮度,凸显灰暗部分的作用。
它的实现原理为我们用结构元素1去覆盖图像2中的矩阵,结构元素可以是十字形,矩形,圆形等。覆盖2后 结构元素中心像素值替换为该覆盖区域图像2中最小的值。
图像腐蚀
Mat src1 = imread("C://Users//Administrator//Desktop//1.jpg");
namedWindow("腐蚀前", 0);//1是自动适应窗口大小,0是可调整
imshow("腐蚀前", src1);
Mat src2 =getStructuringElement(MORPH_CROSS, Size(9,9));//参数为(结构元素形状,结构元素矩阵大小),这里是十字形
cout << endl<<src2 << endl;
Mat src3;
erode(src1, src3, src2);
namedWindow("腐蚀后", 0);
imshow("腐蚀后", src3);
waitKey(0);
结构元素值如下
刚好是一个十字形
它的腐蚀效果如下
我们可以观察到腐蚀后图像元素呈十字形分布,图像整体也暗了许多
而图像膨胀与腐蚀相反,是替换掉最小的值,提高亮度
Mat src1 = imread("C://Users//Administrator//Desktop//1.jpg");
namedWindow("膨胀前", 0);//1是自动适应窗口大小,0是可调整
imshow("膨胀前", src1);
Mat src2 =getStructuringElement(MORPH_CROSS, Size(9,9));
cout << endl<<src2 << endl;
Mat src3;
dilate(src1, src3, src2);
namedWindow("膨胀后", 0);
imshow("膨胀后", src3);
waitKey(0);
图像高亮了许多
现在我们来说明一下图像二值化
二值化先要灰度化,也就是平均RGB颜色
Mat src1 = imread("C://Users//Administrator//Desktop//1.jpg");
namedWindow("灰度化前", 0);//1是自动适应窗口大小,0是可调整
imshow("灰度化前", src1);
Mat src2;
cvtColor(src1, src2, CV_BGR2GRAY);
namedWindow("灰度化", 0);//1是自动适应窗口大小,0是可调整
imshow("灰度化", src2);
waitKey(0);
然后二值化,也就是按阈值的方法黑白处理,有简化图片,便于提取轮廓
Mat src1 = imread("C://Users//Administrator//Desktop//1.jpg");
namedWindow("灰度化前", 0);//1是自动适应窗口大小,0是可调整
imshow("灰度化前", src1);
Mat src2;
cvtColor(src1, src2, CV_BGR2GRAY);
namedWindow("灰度化", 0);//1是自动适应窗口大小,0是可调整
imshow("灰度化", src2);
Mat src3;
threshold(src2, src3,150, 255, 0);//二值化函数,参数为(处理前,处理后,阈值上下限,二值化的方法)其中0-5 共有6种不同的方法 可自行查阅
namedWindow("二值化", 0);//1是自动适应窗口大小,0是可调整
imshow("二值化", src3);
waitKey(0);
在我们拍照的时候,往往受到温度,相机质量,动作稳定等影响容易产生噪点,噪点又分为椒盐噪点(看图很形象)和高斯噪点
下面我们先加椒盐噪点,原理是往图片矩阵中的元素随机黑白化
Mat salt( Mat src2, int n)//n为了保证遍历矩阵 而不是加一次盐就停止
{
for (int k = 0; k < n; k++)//随机取矩阵数组
{
int i = rand() % src2.rows;
int j = rand() % src2.cols;
if (src2.channels() == 1)//如果是单通道
{
src2.at<uchar>(i, j) = 255; //加白盐
}
else//如果是三通道
{
src2.at<Vec3b>(i, j)[0] = 255;
src2.at<Vec3b>(i, j)[1] = 255;
src2.at<Vec3b>(i, j)[2] = 255;
}
}
for (int k = 0; k < n; k++)
{
int i = rand() % src2.rows;
int j = rand() % src2.cols;
if (src2.channels() == 1)
{
src2.at<uchar>(i, j) = 0; //加黑盐
}
else
{
src2.at<Vec3b>(i, j)[0] = 0;
src2.at<Vec3b>(i, j)[1] = 0;
src2.at<Vec3b>(i, j)[2] = 0;
}
}
return src2;
}
int main()
{
Mat src1 = imread("C://Users//Administrator//Desktop//1.jpg");
namedWindow("加盐前", 0);
imshow("加盐前", src1);
Mat src2 = salt(src1, 3000);
namedWindow("加盐后", 0);
imshow("加盐后", src2);
//存储图像
waitKey();
return 0;
}
double gaosirand(double avage, double Variance)//生成高斯随机数 参数是(均值,方差)
{
const double min = numeric_limits<double>::min();//double 最小值
static double s1;//高斯随机数
double s3, s4;//两个普通随机数
do
{
s3 = rand() * (1.0 / RAND_MAX);//rand_max是能产生的最大随机数 这里是求0-1的随机数
s4 = rand() * (1.0 / RAND_MAX);
} while (s3 <= min);
s1 = sqrt(-2.0*log(s3))*cos(2 * CV_PI*s4);//高斯随机数有两种生成方法
//s1 = sqrt(-2.0*log(s4))*sin(2 * CV_PI*s4); //同上
return s1 * Variance + avage;
}
Mat addgaosirand(Mat src1)
{
Mat src2 = src1.clone();
int channels = src1.channels();
int rows = src1.rows;
int cols = src1.cols*channels;// 为了一次性给一列赋值
if (src2.isContinuous())//(矩阵是否存在行为空)
{
cols *= rows;//变成一列
rows = 1;
}
for (int i = 0; i < rows; i++)
{
for (int j = 0; j < cols; j++)
{
int val = src2.ptr<uchar>(i)[j] +gaosirand(2, 0.8) * 32;//高斯随机数访问数组元素,ptr是遍历元素 比at要快
if (val < 0)
val = 0;
if (val > 255)
val = 255;
src2.ptr<uchar>(i)[j] = (uchar)val;//添加高斯噪点
}
}
return src2;
}
int main()
{
Mat src1 = imread("C://Users//Administrator//Desktop//1.jpg");
imshow("原图像", src1);
Mat src2 = addgaosirand(src1);
imshow("加入高斯噪声后的图像", src2);
waitKey();
return 0;
}
下面我们进行滤波去噪
滤波又叫平滑操作,也同样有多种方法,基本思想都是均值化像素,相当于把噪点像素平摊给周围像素,达到弱化噪点的作用,缺点是分摊噪点后图会“糊了”。这里我们采用均值滤波法。其算法原理是
它的含义是取某一像素周围(k.width行,kheight列)个像素相加的平均数取代该像素
int main()
{
Mat src1 = imread("C://Users//Administrator//Desktop//1.jpg");
namedWindow("原图",0);
imshow("原图", src1);
Mat src4;
blur(src1, src4, Size(9, 9));
namedWindow("原图滤波", 0);
imshow("原图滤波", src4);
Mat src2 =imread("C://Users//Administrator//Desktop//2.jpg");
namedWindow("椒盐噪点", 0);
imshow("椒盐噪点", src2);
Mat src5;
blur(src2, src5, Size(9, 9));
namedWindow("椒盐滤波", 0);
imshow("椒盐滤波", src5);
Mat src3= imread("C://Users//Administrator//Desktop//3.jpg");
namedWindow("高斯噪点", 0);
imshow("高斯噪点", src3);
Mat src6;
blur(src3, src6, Size(9, 9));
namedWindow("高斯噪点滤波", 0);
imshow("高斯噪点滤波", src6);
waitKey();
return 0;
}
掩膜mask可理解为抠图,也是与原图进行逻辑“&”运算
Canny算法的原理是设定上下阈值,<下界非边缘,>下界且<上界,又处在其他边界像素周围的一同视为边界,>上界的视为边界
Mat src1 = imread("C://Users//Administrator//Desktop//1.jpg");
namedWindow("原图",0);
imshow("原图", src1);
Mat src2, src3, src4, src5;//src2是src1的灰度图,src3是src3的二值化图,src4是src3第一次提取轮廓,src5是src3第二次地区轮廓并与src1掩膜操作
src2.create(src1.size(), src1.type());
cvtColor(src1, src2, CV_BGR2GRAY);
blur(src2, src4, Size(3, 3));
Canny(src4, src4, 3, 9, 3);//边缘提取算法,上下阈值最好相差3倍,此时src4粗存的只有0和255,0是黑,255是白 其中参数为(输入图,输出边缘结果图,阈值上下限,sobe算子,默认为3*3的卷积矩阵)
namedWindow("轮廓图", 0);
imshow("轮廓图", src4);
src5 = Scalar::all(0);//全黑
src1.copyTo(src5, src4);//src1与src4掩膜给src5 其实就是src4&src1运算,0&src4.element=0,255&src4.element=src4.elementx
namedWindow("轮廓图2", 0);
imshow("轮廓图2", src5);//这次运算轮廓有颜色了
waitKey();
return 0;
梯度:是一个向量,物理意义是某个平面,总有一个方向(x,y,z)使得平面变化最大(也可以理解为平面最陡峭的方向),这个方向就是梯度。我们可以得知平面有无数个点,每一个点都可以朝着方向(cosa,cosb,cosy)拓展,方向导数可以表示为
,而最大的方向导数就是梯度的方向,最大方向导数的值就是梯度的长度,该导数的方向就是梯度的方向。扩展到图中的每一个像素,梯度是图像变化最大的方向,也就是物体轮廓的边界部分。我们建立坐标系(x,y)图像可作为二维函数表示
在x方向上的导数,也就是沿着水平方向的变化
当h=1,也就是移动一个像素的时候得到最小梯度为f(x+1,y)-f(x,y),恰好是向左移动一个单位
在y方向上的导数,也就是沿垂直方向的变化
当h=1,也就是移动一个像素的时候得到最小梯度为f(x,y+1)-f(x,y)向下移动一个像素
梯度的幅值指的是相邻像素的差异比较,结果为梯度的方向角如下,方向角和幅值也能表示梯度的方向
卷积:
假如我们有一张白纸,沿对角线卷起来
面积也s=s1+s2+s3,用积分表示也就是卷积积分
在图像识别当中
原图矩阵f
卷积核矩阵(通常由训练得出特征值)
这里因为(n-i)是递减的,而矩阵的存储顺序是递增的,计算的时候我们要将它反转180°
我们将g矩阵以g的中心元素(这里是1)依次对准f矩阵的每一个元素。这样f矩阵就会与g矩阵整体相重合,f边界外没有行列,g无法重合,我们就计该处f的值为0,然后我们利用公式(这里的i,j是g中的行列数,要求g反转180°)得到结果矩阵s