之前几篇是OpenCV操作图片,这不是OpenCV的卖点,卖点是计算机视觉,所谓视觉,即识别。问题场景简单描述为
输入:图片
输出:对应的物体的位置,数量等信息
计算机识别的整体步骤大概为:
预处理
分割
特征提取
机器学习分类
后期处理
去噪
- 盐噪点(椒盐类似)
Mat lena = Cv2.ImRead("lena.jpg");
// 添加椒盐噪点
Mat salt = new Mat();
lena.CopyTo(salt);
Random rd = new Random();
int n = 5000;// 噪点个数
for (int k = 0; k < n; k++)
{
int i = rd.Next() % salt.Cols;
int j = rd.Next() % salt.Rows;
salt.At<Vec3b>(j, i) = new Vec3b(255, 255, 255);
}


- 均值滤波
在图像上对目标像素给一个模板,该模板包括了其周围的临近像素(如以目标像素为中心的周围8个像素,构成一个滤波模板,即包括目标像素本身),再用模板中的全体像素的平均值来代替原来像素值。
// 均值滤波
Mat blur = new Mat();
Cv2.Blur(salt, blur, new Size(3, 3));
Cv2.ImShow("Blur", blur);

-
中值滤波
中值滤波法是一种非线性平滑技术,它将每一像素点的灰度值设置为该点某邻域窗口内的所有像素点灰度值的中值.
中值滤波
// 中值滤波
Mat median = new Mat();
Cv2.MedianBlur(salt, median, 3);
Cv2.ImShow("median", median);

-
高斯滤波
高斯滤波就是对整幅图像进行加权平均的过程,每一个像素点的值,都由其本身和邻域内的其他像素值经过加权平均后得到。高斯滤波的具体操作是:用一个模板(或称卷积、掩模)扫描图像中的每一个像素,用模板确定的邻域内像素的加权平均灰度值去替代模板中心像素点的值。
高斯
// 高斯滤波
Mat gaussian = new Mat();
Cv2.GaussianBlur(salt, gaussian, new Size(3, 3), 0);// 第三个参数 必须为正奇数,x,y可以不同
Cv2.ImShow("gaussian", gaussian);

-
盒式滤波
盒式滤波1
盒式滤波2
如果归一化,则和均值滤波是一样的
// 盒式滤波
Mat box = new Mat();
Cv2.BoxFilter(salt, box, MatType.CV_8UC3, new Size(5,5));
Cv2.ImShow("box", box);

- 形态学 滤波
-
膨胀
膨胀(dilate)就是求局部的最大值的操作。从数学的角度就是图像与核进行卷积
核可以是任何形状核大小,它拥有一个单独定义出来的参考点,称为锚点,可以把核视为模板或者掩码
数学公式
膨胀 -
腐蚀
腐蚀就是求局部最小值的操作。
腐蚀 - 开闭运算
开运算:先腐蚀后膨胀
闭运算:先膨胀后腐蚀
-
总结:
- 对二值化图像:去掉像素用腐蚀,开运算;增加像素用膨胀,闭运算
- 对灰度图像:灰度值降低,用腐蚀,开运算;灰度值增加用膨胀,闭运算
- 使用先开后比闭的操作,可以有效的去除噪声
-
// 形态学 Open
Mat morphotoOpen = new Mat();
Cv2.MorphologyEx(salt, morphotoOpen, MorphTypes.Open, new Mat());
Cv2.ImShow("morphotoOpen", morphotoOpen);

- 双边滤波
双边滤波(Bilateral filter)是一种非线性的滤波方法,是结合图像的空间邻近度和像素值相似度的一种折中处理,同时考虑空域信息和灰度相似性,达到保边去噪的目的。具有简单、非迭代、局部的特点 [1] 。双边滤波器的好处是可以做边缘保存(edge preserving),一般过去用的维纳滤波或者高斯滤波去降噪,都会较明显地模糊边缘,对于高频细节的保护效果并不明显。
双边滤波器顾名思义比高斯滤波多了一个高斯方差sigma-d,它是基于空间分布的高斯滤波函数,所以在边缘附近,离的较远的像素不会太多影响到边缘上的像素值,这样就保证了边缘附近像素值的保存。但是由于保存了过多的高频信息,对于彩色图像里的高频噪声,双边滤波器不能够干净的滤掉,只能够对于低频信息进行较好的滤波
Mat bilateral = new Mat();
Cv2.BilateralFilter(salt, bilateral, 9, 80, 80);
Cv2.ImShow("bilateral", bilateral);

- 高斯噪点
Mat gaussianNoise = new Mat(lena.Size(), MatType.CV_64FC1);
RNG rng = new RNG((ulong)DateTime.Now.Ticks);
rng.Fill(gaussianNoise, DistributionType.Normal, 0, 15);
Mat yccImg = lena.CvtColor(ColorConversionCodes.BGR2YCrCb);
Mat[] sigMats =Cv2.Split(yccImg);
sigMats[0].ConvertTo(sigMats[0], MatType.CV_64FC1);
sigMats[0] = sigMats[0] + gaussianNoise;
sigMats[0].ConvertTo(sigMats[0], MatType.CV_8UC1); //Y通道加高斯噪声后图像,自动截断小于零和大于255的值
Mat gaussianImg = new Mat(lena.Size(), MatType.CV_8UC3); //添加高斯噪声的图像;
Cv2.Merge(sigMats, gaussianImg);
gaussianImg = gaussianImg.CvtColor(ColorConversionCodes.YCrCb2BGR);
Cv2.ImShow("gaussianImg", gaussianImg);

此处采用的YCrCb格式,然后噪点叠加在Y通道。具体机制,百度YCrCb的介绍即可。
-
均值滤波均值
-
中值滤波中值
-
高斯滤波高斯
-
双边滤波双边
对于不同的噪点,需要调整对应的去噪方式。
去除背景
采用算法去除背景,只留下关注的信息。一张背景图,一张当前图,两张图采用减法或者除法可以去掉背景信息。
- 减法 R=L-I
- 除法 R=255*(1-I/L)
此种方法并不是一个非常好的方法,后续,在动态识别中会介绍更多的去除背景的方法。
二值化
二值图像:只有两种颜色,黑和白,1白色,0黑色
//
// 摘要:
// Applies a fixed-level threshold to each array element.
//
// 参数:
// src:
// input array (single-channel, 8-bit or 32-bit floating point).
//
// dst:
// output array of the same size and type as src.
//
// thresh:
// threshold value.
//
// maxval:
// maximum value to use with the THRESH_BINARY and THRESH_BINARY_INV thresholding
// types.
//
// type:
// thresholding type (see the details below).
//
// 返回结果:
// the computed threshold value when type == OTSU
public static double Threshold(InputArray src, OutputArray dst, double thresh, double maxval, ThresholdTypes type);
//
// 摘要:
// Thresholding type
[Flags]
public enum ThresholdTypes
{
//
// 摘要:
// \f[\texttt{dst} (x,y) = \fork{\texttt{maxval}}{if \(\texttt{src}(x,y) > \texttt{thresh}\)}{0}{otherwise}\f]
Binary = 0,
//
// 摘要:
// \f[\texttt{dst} (x,y) = \fork{0}{if \(\texttt{src}(x,y) > \texttt{thresh}\)}{\texttt{maxval}}{otherwise}\f]
BinaryInv = 1,
//
// 摘要:
// \f[\texttt{dst} (x,y) = \fork{\texttt{threshold}}{if \(\texttt{src}(x,y) > \texttt{thresh}\)}{\texttt{src}(x,y)}{otherwise}\f]
Trunc = 2,
//
// 摘要:
// \f[\texttt{dst} (x,y) = \fork{\texttt{src}(x,y)}{if \(\texttt{src}(x,y) > \texttt{thresh}\)}{0}{otherwise}\f]
Tozero = 3,
//
// 摘要:
// \f[\texttt{dst} (x,y) = \fork{0}{if \(\texttt{src}(x,y) > \texttt{thresh}\)}{\texttt{src}(x,y)}{otherwise}\f]
TozeroInv = 4,
Mask = 7,
//
// 摘要:
// flag, use Otsu algorithm to choose the optimal threshold value
Otsu = 8,
//
// 摘要:
// flag, use Triangle algorithm to choose the optimal threshold value
Triangle = 16
}
- 二值化方式
-
Origin
Origin -
Binary
binary_math.png
binary -
BinaryInv
binarayinv_math
binarayinv.png -
Trunc
trunc_math
trunc -
Tozero
tozero_math
tozero
*TozeroInv
tozeroinv_math
threshtozeroinv
-
- 阈值取值方式
- 手动
Threshold中指定第三个值,即阈值 - Otsu
不用自己指定thresh值,系统会进行计算并且作为返回值返回。
最大类间方差法是由日本学者大津于1979年提出的,是一种自适应的阈值确定的方法,又叫大津法,简称OTSU。它是按图像的灰度特性,将图像分成背景和目标2部分。背景和目标之间的类间方差越大,说明构成图像的2部分的差别越大,当部分目标错分为背景或部分背景错分为目标都会导致2部分差别变小。因此,使类间方差最大的分割意味着错分概率最小。- 原理
- 先计算图像的直方图,即将图像所有的像素点按照0~255共256个bin,统计落在每个bin的像素点数量
- 归一化直方图,也即将每个bin中像素点数量除以总的像素点,使其限制在0~1之间
- 在这里设置一个分类的阈值i,也即一个灰度级,开始从0迭代
- 通过归一化的直方图,统计0~i 灰度级的像素(假设像素值在此范围的像素叫做前景像素) 所占整幅图像的比例w0,并统计前景像素的平均灰度u0;统计i~255灰度级的像素(假设像素值在此范围的像素叫做背景像素) 所占整幅图像的比例w1,并统计背景像素的平均灰度u1;在这里,设图像的总平均灰度为u2,类间方差记为g。
其中:
u2=ω0∗u0+ω1∗u1,g=ω0(u0−μ2)2+ω1(u1−u2)2
将u2带入g中,可得:
g=ω0ω1(u0−u1)2 - ++i,阈值的灰度值加1,并转到第4个步骤,直到i为256时结束迭代
- 将最大g相应的<script type="math/tex" id="MathJax-Element-948">i</script>值作为图像的全局阈值
- 原理
- 手动
参照:https://blog.csdn.net/qq_29462849/article/details/81022607
- Triangle
不用自己指定thresh值,系统会进行计算并且作为返回值返回。
最适用于单个波峰,最开始用于医学分割细胞等。- 原理:
- 图像转灰度
- 计算图像灰度直方图
- 寻找直方图中两侧边界
- 寻找直方图最大值
- 检测是否最大波峰在亮的一侧,否则翻转
- 计算阈值得到阈值T,如果翻转则255-T
- 原理:
参照https://blog.csdn.net/qq_41498261/article/details/102770535
- 测试:
- 手动
- 代码
- 手动
Mat lena = Cv2.ImRead("lena.jpg", ImreadModes.Grayscale);
Cv2.ImShow("lena", lena);
Mat Binary = new Mat();
Cv2.Threshold(lena, Binary, 140, 255, ThresholdTypes.Binary);
Cv2.ImShow("Binary", Binary);
Mat BinaryInv = new Mat();
Cv2.Threshold(lena, BinaryInv, 140, 255, ThresholdTypes.BinaryInv);
Cv2.ImShow("BinaryInv", BinaryInv);
Mat Tozero = new Mat();
Cv2.Threshold(lena, Tozero, 140, 255, ThresholdTypes.Tozero);
Cv2.ImShow("Tozero", Tozero);
Mat TozeroInv = new Mat();
Cv2.Threshold(lena, TozeroInv, 140, 255, ThresholdTypes.TozeroInv);
Cv2.ImShow("TozeroInv", TozeroInv);
Mat Trunc = new Mat();
Cv2.Threshold(lena, Trunc, 140, 255, ThresholdTypes.Trunc);
Cv2.ImShow("Trunc", Trunc);
Cv2.WaitKey();
-
结果
lena.jpg
binray.jpg
binaryinv.jpg
trunc.jpg
tozero.jpg
tozeroinv.jpg - OSTU
- 代码
Mat lena = Cv2.ImRead("lena.jpg", ImreadModes.Grayscale);
Cv2.ImShow("lena", lena);
Mat Binary = new Mat();
double thresh = Cv2.Threshold(lena, Binary, 140, 255, ThresholdTypes.Binary | ThresholdTypes.Otsu);
Console.WriteLine(thresh);
Cv2.ImShow("Binary", Binary);
Mat BinaryInv = new Mat();
thresh = Cv2.Threshold(lena, BinaryInv, 140, 255, ThresholdTypes.BinaryInv | ThresholdTypes.Otsu);
Console.WriteLine(thresh);
Cv2.ImShow("BinaryInv", BinaryInv);
Mat Tozero = new Mat();
thresh = Cv2.Threshold(lena, Tozero, 140, 255, ThresholdTypes.Tozero | ThresholdTypes.Otsu);
Console.WriteLine(thresh);
Cv2.ImShow("Tozero", Tozero);
Mat TozeroInv = new Mat();
thresh = Cv2.Threshold(lena, TozeroInv, 140, 255, ThresholdTypes.TozeroInv | ThresholdTypes.Otsu);
Console.WriteLine(thresh);
Cv2.ImShow("TozeroInv", TozeroInv);
Mat Trunc = new Mat();
thresh = Cv2.Threshold(lena, Trunc, 140, 255, ThresholdTypes.Trunc | ThresholdTypes.Otsu);
Console.WriteLine(thresh);
Cv2.ImShow("Trunc", Trunc);
Cv2.WaitKey();
-
效果
lena
binray
binaryinv
trunc
tozero
tozeroinv
console- Triangle
代码与OSTU基本一致,只是第5个参数后面是| ThresholdTypes.Otsu
其输出值为114
- Triangle

































