假设有如下一张图,如何把其中的文本分块切割出来,比如“华普超市朝阳门店”、“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. MySQL学习——查询表里的数据

    MySQL学习——查询表里的数据 摘要:本文主要学习了使用DQL语句查询表里数据的方法. 数据查询 语法 select [distinct] 列1 [as '别名1'], ..., 列n [as '别 ...

  2. JS基本语法---while循环

    循环:一件事不停的或者是重复的做 循环要有结束的条件,循环还应该有计数器(记录循环的次数的) while循环      while循环语法:   计数器 var 变量=0; while(循环的条件){ ...

  3. 利用InsertStatusValueRequest消息为新增的statuscode设定指定值(Value)

    我是微软Dynamcis 365 & Power Platform方面的工程师罗勇,也是2015年7月到2018年6月连续三年Dynamics CRM/Business Solutions方面 ...

  4. wpf 的dispatcher

    wpf项目中后台代码调用界面控件时,会提示进程调用的错误. private Thread JxThread = null;  //定义线程 private DataLoading.Loading nL ...

  5. 在PyCharm中打开文件的位置

    选中文件,右键选择  Show in Explorer (在资源管理器中显示) 只需要路径时,选择第四个Copy Path ,会复制文件的路径

  6. VSCode 如何同步设置

    微软新推出的 VSCode 是一款开源.轻量.良心的开发工具,一经问世,迅速受到全球广泛开发者的好评与青睐,威风之下有干掉 Sublime Text 的趋势.然而有不少 VSCode 使用者吐槽其不能 ...

  7. echarts使用简介

    1. echarts是百度开源的一个前端图表库 2. 官网地址: https://www.echartsjs.com/zh/index.html 3. 使用介绍: 3.1. 下载echarts.js ...

  8. 3.Python网络编程_多任务问题抛出

    #单线程程序 import time def sing(): """唱歌5秒钟""" for i in range(5): print(&q ...

  9. 1. Git初始

    一.Git初始 1. 定义 ​ 分布式的版本控制系统,在每个使用者电脑上就有一个完整的数据仓库,没有网络依然可以使用Git.当然为了习惯及团队协作,会将本地数据同步到Git服务器或者GitHub等代码 ...

  10. gn gen ninja