假设有如下一张图,如何把其中的文本分块切割出来,比如“华普超市朝阳门店”、“2015-07-26”就是两个文本块。

做图像切割有很多种方法,本文描述一种最直观的投影检测法。先来看看什么是投影,简单来说,投影就是在一定方向上有效像素的数量。来看个直观的图像:

这是一张水平投影图与原图的对比,从投影图上能看到多个波峰,文字多的地方,投影就长,行间的空白处,投影为0。 上个示例代码:

public void HorizontalProjection()
{
//以灰度图方式读入源文件
string filename = "source.jpg";
var src = IplImage.FromFile(filename, LoadMode.GrayScale); //二值化,采用阈值分割法
Cv.Threshold(src, src, , , ThresholdType.BinaryInv | ThresholdType.Otsu); //存储投影值的数组
var h = new int[src.Height]; //对每一行计算投影值
for(int y = ;y < src.Height;++y)
{
//遍历这一行的每一个像素,如果是有效的,累加投影值
for(int x = ;x < src.Width;++x)
{
var s = Cv.Get2D(src, y, x);
if(s.Val0 == )
h[y]++;
}
} //准备一个图像用于画投影图
var paintY = Cv.CreateImage(src.Size, BitDepth.U8, );
Cv.Zero(paintY); //画图
var t = new CvScalar();
for(int y = ;y < src.Height;++y)
{
for(int x = ;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(, , , , ElementShape.Rect);
Cv.Dilate(src, src, kernal, );

再计算投影,得到的效果就好多了:

最后上完整代码以及切割效果展示:

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, );
Cv.CvtColor(src, gray, ColorConversion.BgrToGray); //二值化,阈值分割算法
Cv.Threshold(gray, gray, , , 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(, , );
foreach (var rect in items)
{
Cv.DrawRect(src, rect, color, );
} //显示
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 = ; y < source.Height; ++y)
{
for (int x = ; x < source.Width; ++x)
{
var s = Cv.Get2D(source, y, x);
if (s.Val0 == )
projection[y]++;
}
} bool inLine = false;
int start = ; //开始根据投影值识别分割点
for (int i = ; i < projection.Length; ++i)
{
if (!inLine && projection[i] > )
{
//由空白进入字符区域了,记录标记
inLine = true;
start = i;
}
else if ((i - start > ) && projection[i] < && inLine)
{
//由字符区域进入空白区域了
inLine = false; //忽略高度太小的行,比如分隔线
if (i - start > )
{
//记录下位置
var rect = new CvRect(, start - , source.Width, i - start + );
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(, , , , ElementShape.Rect);
Cv.Dilate(source, source, kernal, ); //遍历每一列计算投影值
for (int x = ; x < source.Width; ++x)
{
for (int y = ; y < source.Height; ++y)
{
var s = Cv.Get2D(source, y, x);
if (s.Val0 == )
projection[x]++;
}
} bool inBlock = false;
int start = ; //开始根据投影值识别分割点
for (int i = ; i < projection.Length; ++i)
{
if (!inBlock && projection[i] >= )
{
//由空白区域进入字符区域了
inBlock = true;
start = i;
}
else if ((i - start > ) && inBlock && projection[i] < )
{
//由字符区域进入空白区域了
inBlock = false; //记录位置,注意由于传入的是source只是一行,因此最终的位置信息要+rowY
if(blocks.Count > )
{
//跟上一个比一下,如果距离过近,认为是同一个文本块,合并
var last = blocks[blocks.Count - ]; if (start - last.X - last.Width <= )
{
blocks.RemoveAt(blocks.Count - );
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;
}
}
}

得到的图像如下,效果还行,将来继续优化吧:

未经许可严禁转载。

最新文章

  1. jQueryMobile示例页面代码
  2. CSS3媒体查询使用小结
  3. InfluxDB学习之InfluxDB数据保留策略(Retention Policies)
  4. Android Studio教程--给Android Studio安装Genymotion插件
  5. bzoj 1193 贪心
  6. linux虚拟机安装
  7. php练习题:投票
  8. ==和equals详解+例子
  9. 【python】 迭代器、生成器、列表推导式
  10. docker+gitlab的安装和迁移
  11. java调用python的惨痛史(无法获取环境变量)
  12. TCP拥塞控制-慢启动、拥塞避免、快重传、快启动
  13. Linux:客户端的实现
  14. (原)torch模型转pytorch模型
  15. Spring boot profile 多环境配置
  16. C++静态成员的应用
  17. 用 node.js 的 hexo 框架搭建一个支持 markdown 的静态博客系统
  18. Mysql 关于not exists一例
  19. 学习CSS制作菜单列表,举一反三
  20. 获取ping的最短、最长、平均时间

热门文章

  1. Spring Boot的注解,你知道或者不知道的都在这里!
  2. JS基础语法---for循环遍历数组
  3. 如何将HTML页面中的文本设置首行缩进
  4. Linux(ubuntu) 一行代码搞定查看文件目录
  5. Android框架Volley使用:Json请求实现
  6. iOS多线程比较
  7. netcore中使用grpc
  8. ENVOIA
  9. ssh-copy-id 命令自动复制本机公钥到远程机器
  10. Ubuntu Terminal「控制台」