假设有如下一张图,如何把其中的文本分块切割出来,比如“华普超市朝阳门店”、“2015-07-26”就是两个文本块。
做图像切割有很多种方法,本文描述一种最直观的投影检测法。先来看看什么是投影,简单来说,投影就是在一定方向上有效像素的数量。来看个直观的图像:
这是一张水平投影图与原图的对比,从投影图上能看到多个波峰,文字多的地方,投影就长,行间的空白处,投影为0。 上个示例代码:
public void HorizontalProjection()
{
//以灰度图方式读入源文件
string filename = "source.jpg";
var src = IplImage.FromFile(filename, LoadMode.GrayScale);
//二值化,采用阈值分割法
Cv.Threshold(src, src, 0, 255, ThresholdType.BinaryInv | ThresholdType.Otsu);
//存储投影值的数组
var h = new int[src.Height];
//对每一行计算投影值
for(int y = 0;y < src.Height;++y)
{
//遍历这一行的每一个像素,如果是有效的,累加投影值
for(int x = 0;x < src.Width;++x)
{
var s = Cv.Get2D(src, y, x);
if(s.Val0 == 255)
h[y]++;
}
}
//准备一个图像用于画投影图
var paintY = Cv.CreateImage(src.Size, BitDepth.U8, 1);
Cv.Zero(paintY);
//画图
var t = new CvScalar(255);
for(int y = 0;y < src.Height;++y)
{
for(int x = 0;x < h[y];++x)
Cv.Set2D(paintY, y, x, t);
}
//显示
using(var window = new CvWindow("Source"))
{
window.Image = src;
using(var win2 = new CvWindow("Projection"))
{
win2.Image = paintY;
Cv.WaitKey();
}
}
}
显然找出波峰对应的y值,就能把行切割开了。 得到一行以后,可以采用类似的思想进行垂直投影,挑了一行测试一下,效果如下:
可以看到效果不是特别好,左右结构的汉字有可能被切开,一个完整的数值也有可能分成多个数字,这种情况需要做一下处理,比如识别的时候要判断如果间距较小就认为仍是同一文本块,或者对图像进行一下横向膨胀处理:
var kernal = Cv.CreateStructuringElementEx(3, 1, 1, 0, ElementShape.Rect);
Cv.Dilate(src, src, kernal, 4);
再计算投影,得到的效果就好多了:
最后上完整代码以及切割效果展示:
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using OpenCvSharp;
using OpenCvSharp.Extensions;
using OpenCvSharp.Utilities;
namespace OpenCvTest
{
class Program
{
static void Main(string[] args)
{
//打开源文件
string filename = "source.jpg";
var src = IplImage.FromFile(filename);
//转成灰度图
var gray = Cv.CreateImage(src.Size, BitDepth.U8, 1);
Cv.CvtColor(src, gray, ColorConversion.BgrToGray);
//二值化,阈值分割算法
Cv.Threshold(gray, gray, 0, 255, ThresholdType.BinaryInv | ThresholdType.Otsu);
//分行
var rows = GetRowRects(gray);
//针对每一行再分块
var items = new List<CvRect>();
foreach (var row in rows)
{
var cols = GetBlockRects(gray.Clone(row), row.Y);
items.AddRange(cols);
}
//把识别出的每一块画到原图上去
var color = new CvScalar(255, 0, 0);
foreach (var rect in items)
{
Cv.DrawRect(src, rect, color, 1);
}
//显示
using (var window = new CvWindow("Image"))
{
window.Image = src;
Cv.WaitKey();
}
}
/// <summary>
/// 识别行
/// </summary>
/// <param name="source"></param>
/// <returns></returns>
private static List<CvRect> GetRowRects(IplImage source)
{
var rows = new List<CvRect>();
//用于存储投影值
var projection = new int[source.Height];
//遍历每一行计算投影值
for (int y = 0; y < source.Height; ++y)
{
for (int x = 0; x < source.Width; ++x)
{
var s = Cv.Get2D(source, y, x);
if (s.Val0 == 255)
projection[y]++;
}
}
bool inLine = false;
int start = 0;
//开始根据投影值识别分割点
for (int i = 0; i < projection.Length; ++i)
{
if (!inLine && projection[i] > 10)
{
//由空白进入字符区域了,记录标记
inLine = true;
start = i;
}
else if ((i - start > 5) && projection[i] < 10 && inLine)
{
//由字符区域进入空白区域了
inLine = false;
//忽略高度太小的行,比如分隔线
if (i - start > 10)
{
//记录下位置
var rect = new CvRect(0, start - 1 , source.Width, i - start + 2);
rows.Add(rect);
}
}
}
return rows;
}
/// <summary>
/// 识别块
/// </summary>
/// <param name="source"></param>
/// <param name="rowY"></param>
/// <returns></returns>
private static List<CvRect> GetBlockRects(IplImage source, int rowY)
{
var blocks = new List<CvRect>();
//用于存储投影值
var projection = new int[source.Width];
//先进行横向膨胀
var kernal = Cv.CreateStructuringElementEx(3, 1, 1, 0, ElementShape.Rect);
Cv.Dilate(source, source, kernal, 4);
//遍历每一列计算投影值
for (int x = 0; x < source.Width; ++x)
{
for (int y = 0; y < source.Height; ++y)
{
var s = Cv.Get2D(source, y, x);
if (s.Val0 == 255)
projection[x]++;
}
}
bool inBlock = false;
int start = 0;
//开始根据投影值识别分割点
for (int i = 0; i < projection.Length; ++i)
{
if (!inBlock && projection[i] >= 2)
{
//由空白区域进入字符区域了
inBlock = true;
start = i;
}
else if ((i - start > 10) && inBlock && projection[i] < 2)
{
//由字符区域进入空白区域了
inBlock = false;
//记录位置,注意由于传入的是source只是一行,因此最终的位置信息要+rowY
if(blocks.Count > 0)
{
//跟上一个比一下,如果距离过近,认为是同一个文本块,合并
var last = blocks[blocks.Count - 1];
if (start - last.X - last.Width <= 5)
{
blocks.RemoveAt(blocks.Count - 1);
var rect = new CvRect(last.X, rowY, i - last.X, source.Height);
blocks.Add(rect);
}
else
{
var rect = new CvRect(start, rowY, i - start, source.Height);
blocks.Add(rect);
}
}
else
{
var rect = new CvRect(start, rowY, i - start, source.Height);
blocks.Add(rect);
} }
}
return blocks;
}
}
}
得到的图像如下,效果还行,将来继续优化吧:
未经许可严禁转载。
亿速云「云服务器」,即开即用、新一代英特尔至强铂金CPU、三副本存储NVMe SSD云盘,价格低至29元/月。点击查看>>
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。