从图像中检测和裁剪文本块(段落)的简单方法?
Easy ways to detect and crop blocks (paragraphs) of text out of image?
我对这个问题做了一些研究,但我认为我的问题与之前提出的问题有很大不同。
我的博士论文涉及对旧词典进行 OCR 并将结果自动转换为类似 XML 的数据库。这部分我已经想通了。但是,我想通过显示用于每个 entry/headword 的扫描片段来丰富最终结果。由于词典长达近9000页,手动完成是不可能的。
这是随机页面的样子:http://i.imgur.com/X2mPZr0.png
由于每个条目总是等于一个段落,我想找到一种方法将每个图像分割成带有文本的矩形(不需要 OCR)作为单独的文件,就像这样(不绘制矩形):
http://i.imgur.com/CWtQD6Q.png
好消息是我的扫描件在形状和大小上都相同,并且在 margins/text 对齐方面相似。每个段落也总是有一个标识。
不好的是我主要是语言学家而不是程序员。我的大部分经验是 Ruby、XML 和 CSS。而且有些段落只有一行。
我知道有一些方法可以做类似的事情:
- Algorithm to detect presence of text on image
- http://www.danvk.org/2015/01/07/finding-blocks-of-text-in-an-image-using-python-opencv-and-numpy.html
- http://answers.opencv.org/question/27411/use-opencv-to-detect-text-blocks-send-to-tesseract-ios/
- https://github.com/kanaadp/iReader
但是我需要大量时间来学习它们(尤其是我在 Python 中的知识为 0)而且我不知道它们是否不仅允许文本检测,还允许段落检测.
任何关于此事的 input/suggestion 将不胜感激,尤其是对新手友好。
我有一些想法要分享...我想我会沿着这些思路进行:
LOW-RESOLUTION复制原图仅供参考
第 1 步 - 黑白门槛
我想我会为此使用 OpenCV 的 Otsu 阈值。
步骤 2 - 寻找垂直黑线
我会对图像每一列中的像素进行平均,然后找到平均值最低的那个,应该是中间的垂直线。下面的代码输出:
Centreline at column: 1635
步骤 3 - 将图像一分为二并 trim 多余的白色 space
步骤 4 - 方框过滤器
我会用一个 55x45 的框进行框过滤,该框与每个段落开头的缩进相匹配,然后是阈值,因此所有段落开头都用黑框标记。
我是 OpenCV 的新手,但已将上述想法编码如下 - 我相信其中很多可以变得更健壮和更高效,所以将其视为概念性的 ;-)
#include <iostream>
#include <opencv2/opencv.hpp>
using namespace cv;
using namespace std;
int
main(int argc,char*argv[])
{
// Load image
Mat orig=imread("page.png",IMREAD_COLOR);
vector<int> PNGwriteOptions;
PNGwriteOptions.push_back(CV_IMWRITE_PNG_COMPRESSION);
PNGwriteOptions.push_back(9);
// Get greyscale and Otsu-thresholded version
Mat bw,grey;
cvtColor(orig,grey,CV_RGB2GRAY);
threshold(grey,bw,0,255,CV_THRESH_BINARY|CV_THRESH_OTSU);
// Find vertical centreline by looking for lowest column average - i.e. darkest vertical bar
Mat colsums;
reduce(bw,colsums,0,CV_REDUCE_AVG);
double min,max;
Point min_loc, max_loc;
minMaxLoc(colsums,&min,&max,&min_loc,&max_loc);
cout << "Centreline at column: " << min_loc.x << endl;
namedWindow("test",CV_WINDOW_AUTOSIZE);
// Split image into left and right
Rect leftROI(0,0,min_loc.x,bw.rows);
Mat leftbw=bw(leftROI);
Rect rightROI(min_loc.x+8,0,bw.cols-min_loc.x-8,bw.rows);
Mat rightbw=bw(rightROI);
imshow("test",leftbw);
waitKey(0);
imshow("test",rightbw);
waitKey(0);
// Trim surrounding whitespace off
Mat Points;
Mat inverted = cv::Scalar::all(255) - leftbw;
findNonZero(inverted,Points);
Rect bRect=boundingRect(Points);
Mat lefttrimmed=leftbw(bRect);
inverted = cv::Scalar::all(255) - rightbw;
findNonZero(inverted,Points);
bRect=boundingRect(Points);
Mat righttrimmed=rightbw(bRect);
imwrite("lefttrimmed.png",lefttrimmed,PNGwriteOptions);
imwrite("righttrimmed.png",righttrimmed,PNGwriteOptions);
// Box filter with 55x45 rectangle to match size of paragraph indent on left
Mat lBoxFilt,rBoxFilt;
boxFilter(lefttrimmed,lBoxFilt,-1,Size(55,45));
normalize(lBoxFilt,lBoxFilt,0,255,NORM_MINMAX,CV_8UC1);
threshold(lBoxFilt,lBoxFilt,254,255,THRESH_BINARY_INV);
imwrite("leftBoxed.png",lBoxFilt,PNGwriteOptions);
}
以防万一您需要帮助来构建此代码 - 似乎 non-trivial 可以编译并且 link 任何反对它的东西 - 我制作了我的 CMakeLists.txt
文件并存储它与源文件位于同一目录中。然后我创建一个名为 build
的 sub-directory 来执行 "out-of-source" 构建,构建过程是:
cd build
cmake ..
make -j 8
./demo
CMakeLists.txt
cmake_minimum_required(VERSION 2.8)
project(demo)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
find_package(OpenCV)
add_executable(demo main.cpp)
target_link_libraries(demo ${OpenCV_LIBS})
关键字:图像处理、书籍、边距、书脊、中心线、页面、折痕、折叠、装订线、装订、拼接、文本、段落、检测、检测。
我对这个问题做了一些研究,但我认为我的问题与之前提出的问题有很大不同。
我的博士论文涉及对旧词典进行 OCR 并将结果自动转换为类似 XML 的数据库。这部分我已经想通了。但是,我想通过显示用于每个 entry/headword 的扫描片段来丰富最终结果。由于词典长达近9000页,手动完成是不可能的。
这是随机页面的样子:http://i.imgur.com/X2mPZr0.png
由于每个条目总是等于一个段落,我想找到一种方法将每个图像分割成带有文本的矩形(不需要 OCR)作为单独的文件,就像这样(不绘制矩形): http://i.imgur.com/CWtQD6Q.png
好消息是我的扫描件在形状和大小上都相同,并且在 margins/text 对齐方面相似。每个段落也总是有一个标识。
不好的是我主要是语言学家而不是程序员。我的大部分经验是 Ruby、XML 和 CSS。而且有些段落只有一行。
我知道有一些方法可以做类似的事情:
- Algorithm to detect presence of text on image
- http://www.danvk.org/2015/01/07/finding-blocks-of-text-in-an-image-using-python-opencv-and-numpy.html
- http://answers.opencv.org/question/27411/use-opencv-to-detect-text-blocks-send-to-tesseract-ios/
- https://github.com/kanaadp/iReader
但是我需要大量时间来学习它们(尤其是我在 Python 中的知识为 0)而且我不知道它们是否不仅允许文本检测,还允许段落检测.
任何关于此事的 input/suggestion 将不胜感激,尤其是对新手友好。
我有一些想法要分享...我想我会沿着这些思路进行:
LOW-RESOLUTION复制原图仅供参考
第 1 步 - 黑白门槛
我想我会为此使用 OpenCV 的 Otsu 阈值。
步骤 2 - 寻找垂直黑线
我会对图像每一列中的像素进行平均,然后找到平均值最低的那个,应该是中间的垂直线。下面的代码输出:
Centreline at column: 1635
步骤 3 - 将图像一分为二并 trim 多余的白色 space
步骤 4 - 方框过滤器
我会用一个 55x45 的框进行框过滤,该框与每个段落开头的缩进相匹配,然后是阈值,因此所有段落开头都用黑框标记。
我是 OpenCV 的新手,但已将上述想法编码如下 - 我相信其中很多可以变得更健壮和更高效,所以将其视为概念性的 ;-)
#include <iostream>
#include <opencv2/opencv.hpp>
using namespace cv;
using namespace std;
int
main(int argc,char*argv[])
{
// Load image
Mat orig=imread("page.png",IMREAD_COLOR);
vector<int> PNGwriteOptions;
PNGwriteOptions.push_back(CV_IMWRITE_PNG_COMPRESSION);
PNGwriteOptions.push_back(9);
// Get greyscale and Otsu-thresholded version
Mat bw,grey;
cvtColor(orig,grey,CV_RGB2GRAY);
threshold(grey,bw,0,255,CV_THRESH_BINARY|CV_THRESH_OTSU);
// Find vertical centreline by looking for lowest column average - i.e. darkest vertical bar
Mat colsums;
reduce(bw,colsums,0,CV_REDUCE_AVG);
double min,max;
Point min_loc, max_loc;
minMaxLoc(colsums,&min,&max,&min_loc,&max_loc);
cout << "Centreline at column: " << min_loc.x << endl;
namedWindow("test",CV_WINDOW_AUTOSIZE);
// Split image into left and right
Rect leftROI(0,0,min_loc.x,bw.rows);
Mat leftbw=bw(leftROI);
Rect rightROI(min_loc.x+8,0,bw.cols-min_loc.x-8,bw.rows);
Mat rightbw=bw(rightROI);
imshow("test",leftbw);
waitKey(0);
imshow("test",rightbw);
waitKey(0);
// Trim surrounding whitespace off
Mat Points;
Mat inverted = cv::Scalar::all(255) - leftbw;
findNonZero(inverted,Points);
Rect bRect=boundingRect(Points);
Mat lefttrimmed=leftbw(bRect);
inverted = cv::Scalar::all(255) - rightbw;
findNonZero(inverted,Points);
bRect=boundingRect(Points);
Mat righttrimmed=rightbw(bRect);
imwrite("lefttrimmed.png",lefttrimmed,PNGwriteOptions);
imwrite("righttrimmed.png",righttrimmed,PNGwriteOptions);
// Box filter with 55x45 rectangle to match size of paragraph indent on left
Mat lBoxFilt,rBoxFilt;
boxFilter(lefttrimmed,lBoxFilt,-1,Size(55,45));
normalize(lBoxFilt,lBoxFilt,0,255,NORM_MINMAX,CV_8UC1);
threshold(lBoxFilt,lBoxFilt,254,255,THRESH_BINARY_INV);
imwrite("leftBoxed.png",lBoxFilt,PNGwriteOptions);
}
以防万一您需要帮助来构建此代码 - 似乎 non-trivial 可以编译并且 link 任何反对它的东西 - 我制作了我的 CMakeLists.txt
文件并存储它与源文件位于同一目录中。然后我创建一个名为 build
的 sub-directory 来执行 "out-of-source" 构建,构建过程是:
cd build
cmake ..
make -j 8
./demo
CMakeLists.txt
cmake_minimum_required(VERSION 2.8)
project(demo)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
find_package(OpenCV)
add_executable(demo main.cpp)
target_link_libraries(demo ${OpenCV_LIBS})
关键字:图像处理、书籍、边距、书脊、中心线、页面、折痕、折叠、装订线、装订、拼接、文本、段落、检测、检测。