目的
Dilate()膨胀函数使用
Erode()腐蚀函数使用
GetStructuringElement()获取结构元素
原理
最常用的形态学运算是膨胀及腐蚀,其它形态学运算是膨胀与腐蚀运算的组合结果。
膨胀是在图像对象(白色,非0)边界增加像素、腐蚀则相反。具体如何增加或减少像素取决于运算的结构元素(也称为核)的大小与形状。具体如下:
膨胀
具体输出的像素值为结构元素中所有1位置对应于输入图像的像素值的最大值,位置由锚点决定。下图为官网示图:用结构元素(核)扫描输入图像,取对应结构元素中为1的任意输入图像中的最大值,填致输出图像对应的锚点位置。
腐蚀
相对膨胀运算是取最大值,而腐蚀运算是取最小值,官网示图如下:
结构元素
结构元素是形态学运算的重要组成部分。一般情况下结构元素是由0和1(击中击不中有-1)构成的任意形态和大小的矩阵,通常比输入图像小的多。
官网图例
这是一张官网处理处理的图像,需要提取水平线及音乐符号。
官网代码示例
/// <summary>
/// 提取音符
/// </summary>
/// <exception cref="Exception"></exception>
private void ExtractNote()
{
var fileName = ImagePath.Music;
using var src = Cv2.ImRead(fileName, ImreadModes.Color);
if (src.Empty()) throw new Exception($"图像打开有误:{
fileName}");
Mat gray = new Mat();
if (src.Channels() == 3)
{
Cv2.CvtColor(src, gray, ColorConversionCodes.BGR2GRAY);
}
else
{
gray = src;
}
Utils.ShowWaitDestroy(gray, "gray");
Mat binMat = new Mat();
//原图为白底黑字,先Gray取反,则Binary
Cv2.AdaptiveThreshold(~gray, binMat, 255, AdaptiveThresholdTypes.MeanC, ThresholdTypes.Binary, 15, -2);
Utils.ShowWaitDestroy(binMat, "binary");
//创建两张图像副本用于提取水平线和垂直线
Mat horizontal = binMat.Clone();
Mat vertical = binMat.Clone();
int times = 30;
//水平轴大小,该值太小的话,文字或符号中的水平线也会被提取
int horizontalSize = horizontal.Cols / times;
//水平线提取的结构元素
Mat horizontalStructure = Cv2.GetStructuringElement(MorphShapes.Rect, new Size(horizontalSize, 1));
Cv2.Erode(horizontal, horizontal, horizontalStructure);
Cv2.Dilate(horizontal, horizontal, horizontalStructure);
//水平线
Utils.ShowWaitDestroy(horizontal, "horizontal");
//垂直轴大小
int verticalSize = vertical.Rows / times;
//垂直线提取的结构元素
Mat verticalStructure = Cv2.GetStructuringElement(MorphShapes.Rect, new Size(1, verticalSize));
//先腐蚀,再膨胀,等价于开运算
Cv2.MorphologyEx(vertical, vertical, MorphTypes.Open, verticalStructure);
//垂直线
Utils.ShowWaitDestroy(vertical, "vertical");
// Inverse vertical image
Cv2.BitwiseNot(vertical, vertical);
Utils.ShowWaitDestroy(vertical, "vertical_bit");
var vClone = vertical.Clone();
// Extract edges and smooth image according to the logic
// 1. extract edges
// 2. dilate(edges)
// 3. src.copyTo(smooth)
// 4. blur smooth img
// 5. smooth.copyTo(src, edges)
// 1、提取边缘
Mat edges = new Mat();
Cv2.AdaptiveThreshold(vertical, edges, 255, AdaptiveThresholdTypes.MeanC, ThresholdTypes.Binary, 3, -2);
Utils.ShowWaitDestroy(edges, "edges");
// 2、膨胀边缘
Mat kernel = Mat.Ones(2, 2, MatType.CV_8UC1);
Cv2.Dilate(edges, edges, kernel);
Utils.ShowWaitDestroy(edges, "Dilate edges");
// 3、待平滑图
Mat smooth = new Mat();
Cv2.CopyTo(vertical, smooth);
// 4、均值平滑
Cv2.Blur(smooth, smooth, new Size(2, 2));
Utils.ShowWaitDestroy(smooth, "Blur smooth");
// 5、复制带边缘掩膜的平滑结果
Cv2.CopyTo(smooth, vertical, edges);
Utils.ShowWaitDestroy(vertical, "smooth-final");
Cv2.DestroyAllWindows();
}
实战:表格及文字提取
根据上面的思路,自己做了一张带文字的表格图片,如下:
实现目标:生成两张图像,一张只有表格,另一张只有文字
实战代码示例
/// <summary>
/// 提取表格及文字
/// </summary>
/// <exception cref="Exception"></exception>
private void ExtractTableAndText()
{
var fileName = ImagePath.TableAndText;
using var src = Cv2.ImRead(fileName, ImreadModes.Color);
if (src.Empty()) throw new Exception($"图像打开有误:{
fileName}");
Mat gray = new Mat();
if (src.Channels() == 3)
{
Cv2.CvtColor(src, gray, ColorConversionCodes.BGR2GRAY);
}
else
{
gray = src;
}
Utils.ShowWaitDestroy(gray, "gray");
Mat binMat = new Mat();
//原图为白底黑字,先Gray取反,则Binary
Cv2.AdaptiveThreshold(~gray, binMat, 255, AdaptiveThresholdTypes.MeanC, ThresholdTypes.Binary, 15, -2);
Utils.ShowWaitDestroy(binMat, "二值图");
//创建两张图像副本用于提取水平和垂直线
Mat horizontal = binMat.Clone();
Mat vertical = binMat.Clone();
//水平轴大小,该值太小的话,文字中的水平线也会被提取
int horizontalSize = 120;//比文字Max(宽、高)大,比单元格Min(高、宽)略小
//水平线提取的结构元素
Mat horizontalStructure = Cv2.GetStructuringElement(MorphShapes.Rect, new Size(horizontalSize, 1));
//先腐蚀,再膨胀,等价于开运算
Cv2.MorphologyEx(horizontal, horizontal, MorphTypes.Open, horizontalStructure);
Utils.ShowWaitDestroy(horizontal, "horizontal");
//垂直轴大小
int verticalSize = horizontalSize;// vertical.Rows / times;
//垂直线提取的结构元素
Mat verticalStructure = Cv2.GetStructuringElement(MorphShapes.Rect, new Size(1, verticalSize));
//开运算
Cv2.MorphologyEx(vertical, vertical, MorphTypes.Open, verticalStructure);
Utils.ShowWaitDestroy(vertical, "vertical");
//表格=水平线 + 垂直线
Mat Table = horizontal.BitwiseOr(vertical);
//将表格稍微膨胀
using var dilateTable = new Mat();
Cv2.Dilate(Table, dilateTable,null);
//源二值图-膨胀后的表格,即为文字内容
Mat Text = binMat - dilateTable;
//平滑边缘
SmoothEdges(Table);
//平滑边缘
SmoothEdges(Text);
}
/// <summary>
/// 平滑边缘
/// </summary>
/// <param name="binSrc"></param>
private void SmoothEdges(Mat binSrc)
{
// Inverse vertical image
Cv2.BitwiseNot(binSrc, binSrc);
Utils.ShowWaitDestroy(binSrc, "binSrc_bit");
// Extract edges and smooth image according to the logic
// 1. extract edges
// 2. dilate(edges)
// 3. src.copyTo(smooth)
// 4. blur smooth img
// 5. smooth.copyTo(src, edges)
// 1、提取边缘
Mat edges = new Mat();
Cv2.AdaptiveThreshold(binSrc, edges, 255, AdaptiveThresholdTypes.MeanC, ThresholdTypes.Binary, 3, -2);
Utils.ShowWaitDestroy(edges, "edges");
// 2、边缘膨胀
Mat kernel = Mat.Ones(2, 2, MatType.CV_8UC1);
Cv2.Dilate(edges, edges, kernel);
Utils.ShowWaitDestroy(edges, "Dilate edges");
// 3、复制源图
Mat smooth = new Mat();
Cv2.CopyTo(binSrc, smooth);
// 4、对源图副本平滑
Cv2.Blur(smooth, smooth, new Size(2, 2));
Utils.ShowWaitDestroy(smooth, "Blur smooth");
// 5、复制带边缘掩膜的平滑结果
Cv2.CopyTo(smooth, binSrc, edges);
Utils.ShowWaitDestroy(binSrc, "smooth -final");
}