分割双页扫描电子书
在平板上阅读双页扫描的电子书是一件很麻烦的事情,但是偏偏很多书都是双页扫描的,大概双页扫描比较方便吧。例如很多书籍的扫描出来的效果是这样的:
我希望能将双页的扫描版转成单页的,但是在网上搜了一圈也没有找到合适的软件,只好自己寻找解决方案了。这问题的复杂性在于,我们不能简单地从中间剪开,因为由于扫描时候的操作,两页并不是正好各占一半的。但是我们可以注意到两页之间有一条细长的黑线,这条黑线可以很自然地成为分割两页的标准线。所以我们可以通过找这条黑线的位置来达到自动分割的功能。其实有很多书扫描的时候由于操作失误,扫出来的书是斜的,对于这种情况,问题就变得很复杂了。
结合自己的情况,我对要处理的扫描图像做出了以下假设:
假设1)扫描图像都是“正”的,也就是说图像没有倾斜或者只是有稍微的倾斜;
假设2)扫描图像都有一条作为分割标准的黑线,并处在图像中间附近;
假设3)扫描图像是黑白的。
黑色的RGB值为(0,0,0),而白色的RGB的值为(255,255,255),所以如果我们将图像每一列的所有像素点的颜色值求一个平均,在黑线附近会达到比较小的值。所以我们可以扫描所有列的平均值挑出最小的列,我们可以期待这一列就是黑线所在的位置。由假设2),我们可以大大提高算法的效率:只要扫描图像中心所在的一个小范围[width/2-range, width/2+range)就可以确定黑线的所在位置,其中width是图像的宽,而range是用来控制范围大小的值,单位是像素。我是用的range值为200。程序如下(使用CImg库):
/* 使用CImg库,以及其命名空间 */
#include "CImg.h"
using namespace cimg_library;
/* 处理扫描图片的核心程序,接受一个文件路径参数 */
int process(const char *filename);
int main(int argc, char **argv)
{
/* 命令行接受处理的文件路径参数
* 如果没有参数,则直接结束程序
*/
if ( argc<2 )
{
return 0;
}
/* 处理图片 */
process(argv[1]);
return 0;
}
/* 核心程序 */
int process(const char *filename)
{
/* 打开图像 */
CImg<unsigned char> img(filename);
/* 获得图像的宽、高参数 */
int width = img.width();
int height = img.height();
/* 设置range值,控制扫描的范围 */
int range = 200;
/* 记录扫描范围内的最小平均值 */
double min_mean = 255;
/* 记录最小平均值的列 */
int column = width/2;
/* 在规定范围内逐列扫描 */
for (int i=width/2-range; i<width/2+range; i++)
{
double mean = img.get_column(i).mean();
if ( min_mean>mean )
{
min_mean = mean;
column = i;
}
}
/* 在平均值最小的列画出红线 */
int line_size = 5; // 控制线条的粗细
const unsigned char red[] = {255, 0, 0}; // 红色
for (int i=column-line_size; i<column+line_size; i++)
{
img.draw_line(i, 0, i, height, red);
}
/* 显示结果 */
img.display();
return 0;
}
下面是上面那个图像的处理结果:
但是这个算法也并不是百分百成功的,下图就是一个反例:
估计导致不准确的原因是中间那条黑线不明显。但是根据我对这本书的统计,170多个图像大概只有10多个图像是没有准确找出黑线的。不是100%的算法就无法做到自动地将所有扫描图像分割。
对于算法的改进,我有两个思路:
思路1)不只是将平均值最小的列拿出来,而是将平均值最小的若干列拿出来,再根据离中间的远近选择一列;
思路2)由于黑线都比较细,我们可以通过平均值两个突变之间的大小来推断黑线的位置。
等什么时候有时间了再回过头看看吧。