目的
Dilate() 拡張関数を使用します
。Erode() 腐食関数を使用します
。 GetStructuringElement() を使用して構造要素を取得します。
原理
最も一般的に使用される形態学的操作は拡張と収縮であり、その他の形態学的操作は拡張と収縮操作の組み合わせです。
膨張では画像オブジェクト (白、ゼロ以外) の境界にピクセルが追加され、浸食ではその逆が行われます。ピクセルがどのように加算または減算されるかは、操作の構造要素 (カーネルとも呼ばれる) のサイズと形状によって異なります。詳細は次のとおりです。
拡大
特定の出力ピクセル値は、入力画像のピクセル値に対応する構造要素内のすべての 1 の位置の最大値であり、その位置はアンカーポイントによって決まります。次の図は公式 Web サイトの図です。構造要素 (カーネル) を使用して入力画像をスキャンし、対応する構造要素の 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();
}
実践的な戦闘: 表とテキストの抽出
上記のアイデアに基づいて、次のようにテキストを含む表画像を作成しました。
目標を達成するには: 2 つの画像を生成します。1 つは表のみ、もう 1 つはテキストのみです。
実践的なコード例
/// <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");
}