OpenCV2.4.13 文本分割(水平垂直,直方图投影)
来源:互联网 发布:淘宝怎么提高排名信誉 编辑:程序博客网 时间:2024/05/17 04:33
进行文字分割时,有多种方法,对与不同情况可以分别处理。
问题1:如何进行文字分割?
答:对于文字是一般正规(不同行的文字一样高,每一行内部文字大致一样宽)的文本的情况。
这里给出了一种方法。
1)对图像二值化
2)对二值化之后的图像进行水平方向投影(找到不同行)
3)利用2)得到的结果对二值化图像切割,然后对每一行进行垂直方向的投影(找到每一行内的不同文字)
4)利用 2)和3)得到的结果画出方框。
本文是与这里的方法对应的C++实现,在这里使用C#实现的。
本文尽量对所使用到的代码进行相近的解释。
- 先读取图片
Mat img = imread(IMG_PATH); if (img.empty()) { cerr<<"can not read image"<<endl; } imshow("original image", img);
显示结果:
- 第一步:1)对图像二值化
// step 1) 对图像二值化,这里因为使用 otsu 必须是单通道, //所以先将图像变成 单通道的图像 Mat gray_img; cvtColor(img,gray_img,CV_BGR2GRAY,1); Mat binary_img; threshold(gray_img,binary_img,90,255,THRESH_OTSU); binary_img = 255 - binary_img; imshow("binary image by otsu", binary_img);
- 第二步:step 2) 对二值化之后的图像进行水平方向投影(找到不同行)
Mat hist_ver; reduce(binary_img/255,hist_ver,1,CV_REDUCE_SUM,CV_32S); int width = 5; int totaln = max(hist_ver.rows,hist_ver.cols); Mat locations = Mat::zeros(3,totaln,CV_32S); int count = 0; Find_begin_end_len(hist_ver,locations,count,width);
问题:reduce()是什么意思呢?
答:reduce()是,将图像,沿着某个方向,做某种“降维”
这里的意思是,将图像沿着横轴做“求和”运算,最后得到的是一个一维向量。
问题:Find_begin_end_len()是什么鬼呢?
答:自己定义的一个函数,找到直方图中相连区域的开始与结束部分的位置。
问题:这个函数的想法是什么呢?
答:输入:一个表示直方图的向量h_vec;
输出:矩阵locations
代码如下:
void Find_begin_end_len(Mat h_vec,Mat& locations,int& count, int width){ // locations 为 3*N 大小的 全零的 Mat, // 经过这个函数处理之后,变成 3*count 大小的矩阵 if (locations.type() != 4 || h_vec.type() != 4) cout <<"locations and h_vec must be type of CV_32S"<<endl; // 将 h_vec 变成一个 一行多列 的矩阵,便于处理 if (h_vec.rows != 1) transpose(h_vec,h_vec); int N = h_vec.cols; int begin, end, len; count = 0; int n = 0; // number of pixels in h_vec[i] for (int i = 0; i < N; i++) { //cout <<" i is: "<< i<<endl; n = h_vec.at<int>(0,i); if (n != 0) { begin = i; for (int j = i; j < N; j++) { n = h_vec.at<int>(0,j); if (n == 0) { end = j-1; len = end - begin; if (len >= width) { locations.at<int>(0,count) = begin; locations.at<int>(1,count) = end; locations.at<int>(2,count) = len; count = count + 1; //// test if the code is right //cout <<" begin is: "<< begin<<endl; //cout <<" end is: "<< end<<endl; //cout <<" len is: "<< len<<endl; //cout <<" count is: "<< count<<endl; } i = j; break; } } } }}
问题:为什么locations and h_vec must be type of CV_32S?
答:因为直方图向量是通过对图像进行的操作是 “求和”,因此新得到的直方图向量中分量的数值可能超出图像像素类型的范围。
这里记录位置的locations 与 直方图向量类似是一致的,因此要用CV_32S了。
问题:为什么一定要是 CV_32S呢?
答:这个,我也不太清楚噢。
不过,CV_32S对应的是 int 型。
- 第三步: step 3)利用2)得到的结果对二值化图像切割,
然后对每一行进行垂直方向的投影(找到每一行内的不同文字)
Mat line; int x,y,height; x = 0; Mat hist_hor; Mat locations2 = Mat::zeros(3,totaln,CV_32S); list<Rect> blocks; // 定义一个链表 list<Rect>::iterator p_list; // 定义一个链表中的迭代器 int count2 = 0; Rect r1; int bx,by,bwid,bhei; width = 2; for (int i = 0; i < count; i++) { y = locations.at<int>(0,i); height = locations.at<int>(2,i); line = binary_img(Rect(x , y , binary_img.cols,height)); reduce(line/255,hist_hor,0,CV_REDUCE_SUM,CV_32S); Find_begin_end_len(hist_hor,locations2,count2,width); // 利用链表存储 Rect 区域 for (int j = 0; j < count2; j++) { bx = locations2.at<int>(0,j); by = locations.at<int>(0,i); bwid = locations2.at<int>(2,j); bhei = locations.at<int>(2,i); r1 = Rect(bx,by,bwid,bhei); blocks.push_back(r1); } }
问题:Rect r1是什么意思?
答:1)Rect 是一种类型,与 int 类似
2)Rect 是一个函数,Rect(x,y, width,height):指定长方形区域,左上角位于(x,y),矩形大小为,常用来指定roi
这里是用来记录,每一个字符所在的位置的。
- 第四步: step 4) 利用 2)和3)得到的结果画出方框。
Scalar color = Scalar(0, 0, 255); for (p_list = blocks.begin(); p_list != blocks.end(); p_list++) rectangle(img,*p_list,color ); imshow("image with box", img);
问题:Scalar color 表示什么意思?
答:Scalar 与 Rect类似,有两重意义。
不过,Scalar 有更多的含义,这里只使用到了最简单的一种。
最终结果:
问题:有些字连在了一起,这个要怎么处理?
答:法一:可以在第一步阈值处理之前或者之后利用形态学滤波做预处理。
不过这样的话,需要引入更多参数。
法二:可以对最终分在一起的一串数字进行后处理。
不过,这样的话,本来错误分在一起的就不能再分开了。
放大招:整体代码如下:
// csdn_code.cpp : 定义控制台应用程序的入口点。//#include "stdafx.h"#include <iostream>#include <opencv2/opencv.hpp>using namespace cv;using namespace std;#define IMG_PATH "..//figures//111.jpg"void Find_begin_end_len(Mat h_vec,Mat& locations,int& count, int width);void Find_begin_end_len(Mat h_vec,Mat& locations,int& count, int width){ // locations 为 3*N 大小的 全零的 Mat, // 经过这个函数处理之后,变成 3*count 大小的矩阵 if (locations.type() != 4 || h_vec.type() != 4) cout <<"locations and h_vec must be type of CV_32S"<<endl; // 将 h_vec 变成一个 一行多列 的矩阵,便于处理 if (h_vec.rows != 1) transpose(h_vec,h_vec); int N = h_vec.cols; int begin, end, len; count = 0; int n = 0; // number of pixels in h_vec[i] for (int i = 0; i < N; i++) { //cout <<" i is: "<< i<<endl; n = h_vec.at<int>(0,i); if (n != 0) { begin = i; for (int j = i; j < N; j++) { n = h_vec.at<int>(0,j); if (n == 0) { end = j-1; len = end - begin; if (len >= width) { locations.at<int>(0,count) = begin; locations.at<int>(1,count) = end; locations.at<int>(2,count) = len; count = count + 1; //// test if the code is right //cout <<" begin is: "<< begin<<endl; //cout <<" end is: "<< end<<endl; //cout <<" len is: "<< len<<endl; //cout <<" count is: "<< count<<endl; } i = j; break; } } } }}int main(){ Mat img = imread(IMG_PATH); if (img.empty()) { cerr<<"can not read image"<<endl; } imshow("original image", img); // step 1) 对图像二值化,这里因为使用 otsu 必须是单通道, //所以先将图像变成 单通道的图像 Mat gray_img; cvtColor(img,gray_img,CV_BGR2GRAY,1); Mat binary_img; threshold(gray_img,binary_img,90,255,THRESH_OTSU); binary_img = 255 - binary_img; imshow("binary image by otsu", binary_img); // step 2) 对二值化之后的图像进行水平方向投影(找到不同行) Mat hist_ver; reduce(binary_img/255,hist_ver,1,CV_REDUCE_SUM,CV_32S); int width = 5; int totaln = max(hist_ver.rows,hist_ver.cols); Mat locations = Mat::zeros(3,totaln,CV_32S); int count = 0; Find_begin_end_len(hist_ver,locations,count,width); // step 3)利用2)得到的结果对二值化图像切割, // 然后对每一行进行垂直方向的投影(找到每一行内的不同文字) Mat line; int x,y,height; x = 0; Mat hist_hor; Mat locations2 = Mat::zeros(3,totaln,CV_32S); list<Rect> blocks; // 定义一个链表 list<Rect>::iterator p_list; // 定义一个链表中的迭代器 int count2 = 0; Rect r1; int bx,by,bwid,bhei; width = 2; for (int i = 0; i < count; i++) { y = locations.at<int>(0,i); height = locations.at<int>(2,i); line = binary_img(Rect(x , y , binary_img.cols,height)); reduce(line/255,hist_hor,0,CV_REDUCE_SUM,CV_32S); Find_begin_end_len(hist_hor,locations2,count2,width); // 利用链表存储 Rect 区域 for (int j = 0; j < count2; j++) { bx = locations2.at<int>(0,j); by = locations.at<int>(0,i); bwid = locations2.at<int>(2,j); bhei = locations.at<int>(2,i); r1 = Rect(bx,by,bwid,bhei); blocks.push_back(r1); } } // step 4) 利用 2)和3)得到的结果画出方框。 Scalar color = Scalar(0, 0, 255); for (p_list = blocks.begin(); p_list != blocks.end(); p_list++) rectangle(img,*p_list,color ); imshow("image with box", img); waitKey(); system("pause"); return 0;}
问题:如果有大小不一样的文字怎么办呢?
比如这种:
利用大招中的方法得到的结果是如下:
答:利用这里的方法。
- OpenCV2.4.13 文本分割(水平垂直,直方图投影)
- 水平垂直投影
- 水平垂直投影
- 基于水平投影,垂直投影的字符图像分割思路和代码实现
- 使用OpenCV2.x计算图像的水平和垂直积分投影
- 使用OpenCV2.x计算图像的水平和垂直积分投影
- 水平分割和垂直分割
- OPENCV笔记(四)垂直灰度直方图投影
- 水平投影和垂直投影的用处
- opencv 垂直投影分割字符
- 水平垂直分割详细介绍
- 图像的水平与垂直积分投影
- 图像的水平与垂直积分投影
- 数据库中的水平分割和垂直分割
- 数据库中的水平分割和垂直分割
- 数据库中的水平分割和垂直分割
- 数据库中的水平分割和垂直分割
- 数据库中的水平分割和垂直分割
- vue2.0_饿了么——购物车组件
- 串口工具删除系统应用
- js-多物体运动框架
- ARM 伪指令和 ARM 宏指令
- 正反向隔离装置(网闸)
- OpenCV2.4.13 文本分割(水平垂直,直方图投影)
- 12月14日考试题反思
- 克服懒惰
- C++11常用特性的使用经验总结
- sql错删表之后的操作
- gym101628 problemD Don't Lose The Droid 水题 瞎写
- oracle自动添加分区表分区
- opencv图片压缩后读取的像素值,和保存后读出来的像素值不一样的问题。
- iptables详解