假设有如下一张图,如何把其中的文本分块切割出来,比如“华普超市朝阳门店”、“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投影法进行文本分块切割的更多相关文章

  1. Java基于opencv实现图像数字识别(五)—投影法分割字符

    Java基于opencv实现图像数字识别(五)-投影法分割字符 水平投影法 1.水平投影法就是先用一个数组统计出图像每行黑色像素点的个数(二值化的图像): 2.选出一个最优的阀值,根据比这个阀值大或小 ...

  2. Java基于opencv实现图像数字识别(二)—基本流程

    Java基于opencv实现图像数字识别(二)-基本流程 做一个项目之前呢,我们应该有一个总体把握,或者是进度条:来一步步的督促着我们来完成这个项目,在我们正式开始前呢,我们先讨论下流程. 我做的主要 ...

  3. Java基于opencv实现图像数字识别(一)

    Java基于opencv实现图像数字识别(一) 最近分到了一个任务,要做数字识别,我分配到的任务是把数字一个个的分开:当时一脸懵逼,直接百度java如何分割图片中的数字,然后就百度到了用Buffere ...

  4. 【Gabor】基于多尺度多方向Gabor融合+分块直方图的表情识别

    Topic:表情识别Env: win10 + Pycharm2018 + Python3.6.8Date:   2019/6/23~25 by hw_Chen2018                  ...

  5. [转载]卡尔曼滤波器及其基于opencv的实现

    卡尔曼滤波器及其基于opencv的实现 源地址:http://hi.baidu.com/superkiki1989/item/029f65013a128cd91ff0461b 这个是维基百科中的链接, ...

  6. 基于Opencv和Mfc的图像处理增强库GOCVHelper(索引)

    GOCVHelper(GreenOpen Computer Version Helper )是我在这几年编写图像处理程序的过程中积累下来的函数库.主要是对Opencv的适当扩展和在实现Mfc程序时候的 ...

  7. 基于OpenCv的人脸检测、识别系统学习制作笔记之一

    基于OpenCv从视频文件到摄像头的人脸检测 在OpenCv中读取视频文件和读取摄像头的的视频流然后在放在一个窗口中显示结果其实是类似的一个实现过程. 先创建一个指向CvCapture结构的指针 Cv ...

  8. 基于opencv网络摄像头在ubuntu下的视频获取

     基于opencv网络摄像头在ubuntu下的视频获取 1  工具 原料 平台 :UBUNTU12.04 安装库  Opencv-2.3 2  安装编译运行步骤 安装编译opencv-2.3  参 ...

  9. 基于opencv的小波变换

    基于opencv的小波变换 提供函数DWT()和IDWT(),前者完成任意层次的小波变换,后者完成任意层次的小波逆变换.输入图像要求必须是单通道浮点图像,对图像大小也有要求(1层变换:w,h必须是2的 ...

随机推荐

  1. 安卓开发笔记(三十五):Cardview的简单使用

    首先上图: 我们可以看到上面这个我所编写的界面上,战狼这一个模块则使用了cardview控件,下面我们来看看它是怎么使用的:这里是cardview在线性布局下的的布局代码: <android.s ...

  2. sql使用cte表达式进行递归查询

    --递归获取所有子节点 with temp as ( select * from MK_Base_Department where F_DepartmentId='5f258320-c1b7-42a4 ...

  3. FileProvider的使用

    还望支持个人博客站:http://www.enjoytoday.cn 概述 Android 7.0的新特性规定,对于android 7.0应用(仅仅对于android 7.0版本的sdk而言,若是编译 ...

  4. python函数修饰符@的使用

    python函数修饰符@的作用是为现有函数增加额外的功能,常用于插入日志.性能测试.事务处理等等. 创建函数修饰符的规则:(1)修饰符是一个函数(2)修饰符取被修饰函数为参数(3)修饰符返回一个新函数 ...

  5. python 逐行读取txt文件

    逐行读取txt文件 path = r'D:\123456\1.txt'with open(path, 'r', encoding='utf-8') as f:    for line in f:   ...

  6. 在MVC视图中将数字转换为string类型后保留两位小数

    <td>@item.recharge_reward_rate.ToString("F2")%</td> @*保留小数两位*@ <td>@item ...

  7. [PHP] 现代化PHP之路:composer的安装和升级

    1.下载一个脚本文件 wget https://getcomposer.org/installer 2.php执行下这个php脚本 php installer 3.把下载的文件转移到一个PATH环境变 ...

  8. 矩阵补全(Matrix Completion)和缺失值预处理

    目录 1 常用的缺失值预处理方式 1.1 不处理 1.2 剔除 1.3 填充 2 利用矩阵分解补全缺失值 3 矩阵分解补全缺失值代码实现 4 通过矩阵分解补全矩阵的一些小问题 References 矩 ...

  9. Html学习之七(CSS选择器的使用--基础选择器优先级问题)

    二.基础选择器的综合使用 优先级顺序:id选择器>class选择器>元素选择器.也就是说,如果这三种选择器同时为某一个元素设定样式,那么冲突的部分按优先级的顺序依次决定. <!DOC ...

  10. tomcat快速入门

    简介 Tomcat 是什么 Tomcat 是由 Apache 开发的一个 Servlet 容器,实现了对 Servlet 和 JSP 的支持,并提供了作为Web服务器的一些特有功能,如Tomcat管理 ...