基于OpenCV.Net投影法进行文本分块切割
假设有如下一张图,如何把其中的文本分块切割出来,比如“华普超市朝阳门店”、“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;
}
}
}
得到的图像如下,效果还行,将来继续优化吧:

未经许可严禁转载。
基于OpenCV.Net投影法进行文本分块切割的更多相关文章
- Java基于opencv实现图像数字识别(五)—投影法分割字符
Java基于opencv实现图像数字识别(五)-投影法分割字符 水平投影法 1.水平投影法就是先用一个数组统计出图像每行黑色像素点的个数(二值化的图像): 2.选出一个最优的阀值,根据比这个阀值大或小 ...
- Java基于opencv实现图像数字识别(二)—基本流程
Java基于opencv实现图像数字识别(二)-基本流程 做一个项目之前呢,我们应该有一个总体把握,或者是进度条:来一步步的督促着我们来完成这个项目,在我们正式开始前呢,我们先讨论下流程. 我做的主要 ...
- Java基于opencv实现图像数字识别(一)
Java基于opencv实现图像数字识别(一) 最近分到了一个任务,要做数字识别,我分配到的任务是把数字一个个的分开:当时一脸懵逼,直接百度java如何分割图片中的数字,然后就百度到了用Buffere ...
- 【Gabor】基于多尺度多方向Gabor融合+分块直方图的表情识别
Topic:表情识别Env: win10 + Pycharm2018 + Python3.6.8Date: 2019/6/23~25 by hw_Chen2018 ...
- [转载]卡尔曼滤波器及其基于opencv的实现
卡尔曼滤波器及其基于opencv的实现 源地址:http://hi.baidu.com/superkiki1989/item/029f65013a128cd91ff0461b 这个是维基百科中的链接, ...
- 基于Opencv和Mfc的图像处理增强库GOCVHelper(索引)
GOCVHelper(GreenOpen Computer Version Helper )是我在这几年编写图像处理程序的过程中积累下来的函数库.主要是对Opencv的适当扩展和在实现Mfc程序时候的 ...
- 基于OpenCv的人脸检测、识别系统学习制作笔记之一
基于OpenCv从视频文件到摄像头的人脸检测 在OpenCv中读取视频文件和读取摄像头的的视频流然后在放在一个窗口中显示结果其实是类似的一个实现过程. 先创建一个指向CvCapture结构的指针 Cv ...
- 基于opencv网络摄像头在ubuntu下的视频获取
基于opencv网络摄像头在ubuntu下的视频获取 1 工具 原料 平台 :UBUNTU12.04 安装库 Opencv-2.3 2 安装编译运行步骤 安装编译opencv-2.3 参 ...
- 基于opencv的小波变换
基于opencv的小波变换 提供函数DWT()和IDWT(),前者完成任意层次的小波变换,后者完成任意层次的小波逆变换.输入图像要求必须是单通道浮点图像,对图像大小也有要求(1层变换:w,h必须是2的 ...
随机推荐
- 漫谈golang设计模式 工厂模式
工厂模式 意义:创建过程交给专门的工厂子类去完成.定义一个抽象的工厂类,再定义具体的工厂类来生成子类等,它们实现在抽象按钮工厂类中定义的方法.这种抽象化的结果使这种结构可以在不修改具体工厂类的情况下引 ...
- Dynamics CRM 客户端程序开发:在实体的列表界面添加按钮
关注本人微信和易信公众号: 微软动态CRM专家罗勇 ,回复114或者20140312可方便获取本文,同时可以在第一时间得到我发布的最新的博文信息,follow me! 如果没有安装Ribbon Wor ...
- [转]Doing more with Outlook filter and SQL DASL syntax
本文转自:https://blogs.msdn.microsoft.com/andrewdelin/2005/05/10/doing-more-with-outlook-filter-and-sql- ...
- PWA入门:手把手教你制作一个PWA应用
摘要: PWA图文教程 原文:PWA入门:手把手教你制作一个PWA应用 作者:MudOnTire Fundebug经授权转载,版权归原作者所有. 简介 Web前端的同学是否想过学习app开发,以弥补自 ...
- MySQL分组查询统计
GROUP BY 1.有个店铺表,字段为区域id,设备数量,店铺名称,如下: 2.如何按区域 district 统计 每个区域的设备数量,SQL如下 SELECT district, SUM( dev ...
- 函数的名称空间,函数的嵌套(global, nonlocal),函数名的运用
一 函数的名称空间 内置空间:存放python解释器内置函数的空间 全局空间:py文件运行时开辟的,存放的是执行的py文件(出去函数内部)的所有的变量与值的对用关系,整个py文件结束后才会消失. 局部 ...
- Linux:别名的设置
作用 别名的作用是将较长的命令做简化 定义别名 alias [别名[=原命令]] 删除别名 unalias 查看系统可用别名 alias
- 1.Java基础_Java核心机制简介
Java的两种核心机制 Java虚拟机机制 Java垃圾回收机制 解释名词 J2SDK&JRE: J2SDK=JDK=Software Development Kit(软件开发包) JRE=J ...
- python3.5.3rc1学习三:文件操作
##全局变量与局部变量x = 6 def printFuc(): y = 8 z =9 print(y + z) print(x) printFuc()#print(y)#常见错误##name = & ...
- python3.5.3rc1学习一
print ("Hello Pythoh3")print('我喜欢"香蕉"')print('we\'ar go to shoping.')print(" ...