假设有如下一张图,如何把其中的文本分块切割出来,比如“华普超市朝阳门店”、“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. JS基础语法---分支语句总结

    分支语句: if语句:一个分支 if-else语句:两个分支,最终只执行一个分支 if-else if-else if...语句: 多个分支,也是只会执行一个 switch-case语句:多分支语句, ...

  2. Android Activity 开发常用技巧整理

    1.设置 Activity 背景色为透明 在style.xml里面声明: <style name="TranslucentActivityStyle" parent=&quo ...

  3. 2_Swift基本数据类型

    数字和基本数据类型 模型数据与数字,布尔值和其他基本类型. 逻辑值 struct Bool 一个值类型实例, 取值true或者flase Bool表示Swift中的布尔值.Bool通过使用其中一个布尔 ...

  4. idea中git标签(tag)的创建与使用

    1.什么是标签 通常,发布一个版本时,会在版本库中打一个标签(tag),这样,就唯一确定了打标签时刻的版本.将来无论什么时候,取某个标签的版本,就是把那个打标签的时刻的历史版本取出来. 所以,标签也是 ...

  5. PHP代码篇(二)-- array_column函数将二维数组格式化成固定格式的一维数组,及优化查询方法

    小白因为经常用到多表查询,比如获取一个会员领取的卡卷list,里面当然包含了1“会员优惠券记录表t_coupon_members”主表,然后2“门店优惠券表t_coupon”,和3“门店信息表t_sh ...

  6. LCD RGB 控制技术 时钟篇(下)【转】

    上一篇博文,我们介绍了LCD RGB控制模式的典型时钟.那么这一片我们要详细的去讨论剩下的细节部分. 我们先回顾一下之前的典型时序图 在这个典型的时序图里面,除了上篇博文讲述的HSYNC VSYNC ...

  7. C++ 基础语法 快速复习笔记(1)

    最近要刷题,重温一下C++基本的概念...233 1.概念: C++ 是一种静态类型的.编译式的.通用的.大小写敏感的.不规则的编程语言,支持过程化编程.面向对象编程和泛型编程. C++ 被认为是一种 ...

  8. [转]5 种使用 Python 代码轻松实现数据可视化的方法

    数据可视化是数据科学家工作中的重要组成部分.在项目的早期阶段,你通常会进行探索性数据分析(Exploratory Data Analysis,EDA)以获取对数据的一些理解.创建可视化方法确实有助于使 ...

  9. 如何让table中td与四周有间距

    如何让table中td与四周有间距 方法一 在td下再添加一个会计元素 <tr> <td>第2节</td> <td>语文</td> < ...

  10. day52_9_16Django中的静态文件和orm

    一.静态文件配置 在配置静态文件时,需要创建一个文件夹在Django项目文件夹下,名字与使用无关. 静态文件包括html等使用的不会变动的插件文件等.分为三个部分: css文件夹 当前网站所有的样式文 ...