【Lecture 5.3】Release the Kraken

2023-05-16

文章目录

    • Release the Kraken

Release the Kraken

我们要看的下一个库是 Kraken,它是由巴黎PSL大学开发的。我们将使用Kraken的目的是在给定图像中检测文本行来作为的边界框(detect lines of text as bounding boxes), tesseract 的最大局限在于其内部缺少布局(layout)引擎。 Tesseract 希望输入 clean 的文本图像,并且,如果我们不 crop up 其他artifacts,它就可能无法正确处理,但是Kraken可以帮助我们分割页面。 让我们来看看。

首先我们来看看 Kranken 模块

import kraken
help(kraken)
----
Help on package kraken:

NAME
    kraken - entry point for kraken functionality

PACKAGE CONTENTS
    binarization
    ketos
    kraken
    lib (package)
    linegen
    pageseg # 
    repo
    rpred
    serialization
    transcribe

DATA
    absolute_import = _Feature((2, 5, 0, 'alpha', 1), (3, 0, 0, 'alpha', 0...
    division = _Feature((2, 2, 0, 'alpha', 2), (3, 0, 0, 'alpha', 0), 8192...
    print_function = _Feature((2, 6, 0, 'alpha', 2), (3, 0, 0, 'alpha', 0)...

FILE
    /opt/conda/lib/python3.7/site-packages/kraken/__init__.py

这里没有太多讨论,但是有许多看起来很有趣的子模块。 我花了一些时间在他们的网站上,我认为处理所有页面细分的 the pageseg module 是我们要使用的模块。 让我们看看

from kranken import pageseg
help(pageseg)

因此,看起来我们可以调用一些不同的函数,而 the segment function 看起来特别合适。

 segment(im, text_direction='horizontal-lr', scale=None, maxcolseps=2, black_colseps=False, no_hlines=True, pad=0, mask=None)
        # Segments a page into text lines.
        
        Segments a page into text lines and returns the absolute coordinates of
        each line in reading order.
        ...
        Args:
            im (PIL.Image): A bi-level page of mode '1' or 'L'
            text_direction (str): Principal direction of the text
                                  (horizontal-lr/rl/vertical-lr/rl)
            scale (float): Scale of the image
            maxcolseps (int): Maximum number of whitespace column separators
            black_colseps (bool): Whether column separators are assumed to be
                                  vertical black lines or not # 列分隔符是否为垂直黑线
            no_hlines (bool): Switch for horizontal line removal
            pad (int or tuple): Padding to add to line bounding boxes. If int the
                                same padding is used both left and right. If a
                                2-tuple, uses (padding_left, padding_right).
            mask (PIL.Image): A bi-level mask image of the same size as `im` where
                              0-valued regions are ignored for segmentation
                              purposes. Disables column detection.
        Returns:
            {'script_detection': True, 'text_direction': '$dir', 'boxes':
            [[(script, (x1, y1, x2, y2)),...]]}: A dictionary containing the text
            direction and a list of lists of reading order sorted bounding boxes
            under the key 'boxes' with each list containing the script segmentation
            of a single line. Script is a ISO15924 4 character identifier.

我喜欢这个库在文档方面的表现力-我可以立即看到我们正在使用PIL.Image文件,并且作者甚至指示我们需要传递二值图(e.g. ‘l’)或灰度图 (e.g. ‘L’)。 我们还可以看到,返回值是带有两个键的字典对象,“ text_direction”将返回文本方向的字符串,“ boxes”将显示为元组列表,其中每个元组为 a box iln the original image.。

让我们在文本图像上尝试一下。 我在一个名 为 two_col.png 的文件中有一段简单的文字,该文件来自校园的报纸

from PIL import Image
im = Image.open('readonly/two_col.png')
# 显示图片
display(im)
# 现在让我们将其转换为黑白,并用kranken包把它分成几行
bounding_boxes = pageseg.segment(im.convert('l'))['boxes']
# 然后我们把 box 打印出来:box的左上和右下的坐标
print(bounding_boxes)
[[100, 50, 449, 74], [131, 88, 414, 120], [59, 196, 522, 229], [18, 239, 522, 272], [19, 283, 522, 316], [19, 327, 525, 360], [19, 371, 523, 404], [18, 414, 524, 447], [17, 458, 522, 491], [19, 502, 141, 535], [58, 546, 521, 579], [18, 589, 522, 622], [19, 633, 521, 665], [563, 21, 1066, 54], [564, 64, 1066, 91], [563, 108, 1066, 135], [564, 152, 1065, 179], [563, 196, 1065, 229], [563, 239, 1066, 272], [562, 283, 909, 316], [600, 327, 1066, 360], [562, 371, 1066, 404], [562, 414, 1066, 447], [563, 458, 1065, 485], [563, 502, 1065, 535], [562, 546, 1066, 579], [562, 589, 1064, 622], [562, 633, 1066, 660], [18, 677, 833, 704], [18, 721, 1066, 754], [18, 764, 1065, 797], [17, 808, 1065, 841], [18, 852, 1067, 885], [18, 895, 1065, 928], [17, 939, 1065, 972], [17, 983, 1067, 1016], [18, 1027, 1065, 1060], [18, 1070, 1065, 1103], [18, 1114, 1065, 1147]]

image-20200509233703620

好吧,非常简单的两列文本,然后是列表,这些列表是该文本行的边界框(a list of lists which are the bounding boxes of lines of that text. )让我们编写一些例程以尝试更清楚地看到效果。 我将整理一下自己的行为并编写真实的文档,这是一个好习惯

def show_boxes(img):
    '''修改传递的图像,以显示由kraken运行的图像上的一系列边界框
    :param img: A PIL.Image object
    :return img: The modified PIL.Image object'''
    # 引入 PIL 模块的 draw 对象 以对获取到的坐标进行方框绘制
    from PIL import ImageDraw
    drawing_object = ImageDraw.Draw(img)
    # 使用 pageseg.segment 方法创建一系列方框坐标
    bounding_boxes = pages.segment(img.convert('l'))['boxes']
    # 遍历这个(包含方框坐标元组)的列表
    for box in bounding_boxes:
        drawing_object.rectangle(box, fill = None, outline = 'red')
        
    return img

# 使用display 展示变更过后的 图像
display(show_boxes(Image.open('readonly/two_col.png')))

image-20200510101344089

很不错! 有趣的是,kraken不能完全确定如何处理这两列格式的文本。 在某些情况下,kraken 在每一单列中标识了一条线,而在其他情况下,kraken把两列文本识别为一行。 这有关系吗? 好吧,这确实取决于我们的目标。 在这种情况下,我想看看我们是否可以对此有所改进。

因此,我们将在此处不说脚本。 尽管本周的讲座是关于库的,但最后一门课程的目标是使您充满信心,即使您使用的库不能完全满足您的要求,也可以将您的知识应用于实际的编程任务。看上图,带有两列示例和红色框,您认为我们如何修改此图像以提高kraken的文本行能力?

感谢您分享您的想法,我期待看到课程中每个人都提出的广泛想法。 这是我的解决方案-在浏览 pageseg() 函数上的kraken文档时,我看到有一些参数来提高segmentation的效果。 其中之一是 black_colseps 参数。 如果设置为True,则kraken将假定各列是用黑线分隔。 我们这里并没有用黑色竖线分割两列,但是,我认为我们可以将源图像更改为在列之间使用黑色分隔符。

我们在

def show_boxes(img):
    # Lets bring in our ImageDraw object
    from PIL import ImageDraw
    # And grab a drawing object to annotate that image
    drawing_object=ImageDraw.Draw(img)
    # 增加一个文本分割的参数 black_colseps=True
    bounding_boxes=pageseg.segment(img.convert('1'), black_colseps=True)['boxes']
    # Now lets go through the list of bounding boxes
    for box in bounding_boxes:
        # An just draw a nice rectangle
        drawing_object.rectangle(box, fill = None, outline ='red')
    # And to make it easy, lets return the image object
    return img

下一步是考虑我们要检测空白列分隔符(detect a white column separator)的算法。 在进行一些实验时,我决定仅在的间隔至少为25个像素宽(大约是一个字符的宽度,六倍行高)时才认为这是分隔符。 宽度很容易,让我们做一个变量

char_width = 25
# 高度较难,因为它取决于文本的高度。 我将编写一个例程来计算一行的平均高度
def calculate_line_height(img):
    '''计算 一个image 的 the average height of a line 
    :param img: A PIL.Image object
    :retuen: The average line height in pixels'''
    # 首先得到 bounding box 坐标的列表
    bounding_boxes = pageseg.segment(img.convert('l'))['boxes']
    # each box is a tuple of (top, left, bottom, right)
    # 因此每行的高度是(top-bottom)
    height_acculator = 0
    for box in bounding_boxes:
        height_accmulator = height_accumlator + box[3]-box[1]
        # 这有点棘手,请记住,我们从PIL的左上角开始计数! 现在让我们只返回平均高度,就可以通过将其变为整数来将其更改为最接近的完整像素
    return int(height_accumulator/len(bounding_boxes))

# 让我们测试下每行的平均高度
line_height = calculate_line_height(Image.open('readonly/two_col.png'))
print(line_height)
31

好的,所以一条线的平均高度是31。现在,我们要扫描图像-依次查看每个像素:以确定是否存在空白的区域(whitespace)。 How bit of a block should we look for? 那是一门艺术,而不是一门科学。 看我们的示例图像,我要说一个合适的块的大小应该是 one char_width wide, and six line_heights tall. 但是我也只是目测的, 让我们创建一个新的框,称为间隙框(gap_box),代表该区域

# Lets create a new box called gap box that represents this area
gap_box=(0,0,char_width,line_height*6)
gap_box
---
(0, 0, 25, 186)

我们将希望有一个函数,输入一幅图像中的像素,可以检查该像素是否在其右侧和下方有whitespace。 本质上,我们要测试像素是否是 gap_box 的对象的左上角。 If so, we should insert a line to “break up” this box before sending to kraken

回想一下,我们可以使用 img.getpixel() 函数获得一个坐标的像素值。 它以整数元组的形式返回此值,每个颜色通道一个。 当适用于二值化图像(黑白),将只返回一个值。 如果值为0,则为黑色像素;如果为白色,则值为255
我们将假设图像已被二值化。 检查边界框的算法非常简单:我们有一个位置作为起点,然后我们要检查该位置右边的所有像素,直到 gap_box[2]

# Lets call this new function gap_check
def gap_check(img, location):
    '''Checks the img in a given (x,y) location to see if it fits the description
    of a gap_box
    :param img: A PIL.Image file
    :param location: A tuple (x,y) which is a pixel location in that image
    :return: True if that fits the definition of a gap_box, otherwise False
    '''
    for x in range(location[0], location[0]+gap_box[2]):
        # the height is similar, so lets iterate a y variable to gap_box[3]
        for y in range(location[1], location[1]+gap_box[3]):
            # 我们想要check这个区域内的像素是不是白色,如果是白色我们什么都不做,
            # 如果是黑色,return False 说明从这个坐标点爱看i是的 block区域内不是 列间隔区域
            # 我们最好确认这在图像内
            if x < img.width and y < img.height:
                if img.getpixel((x,y)) != 255:
                    return False
    # 如果我们遍历了所有的以 loaction 为起点的 gap_box 则这就是一个gap
    return True

好的现在我们有了一个 function 来 check 列文本图像中的列gap,当我们找到这个 gap 之后我们想要做的是在这个 gap_box 的正中间画一条线,让我们写一个function来完成这个工作

def draw_sep(img,location):
    '''Draws a line in img in the middle of the gap discovered at location. Note that
    this doesn't draw the line in location, but draws it at the middle of a gap_box
    starting at location.
    :param img: A PIL.Image file
    :param location: A tuple(x,y) which is a pixel location in the image
    '''
    # First 画 操作的代码段 
    from PIL import ImageDraw
    drawing_object=ImageDraw.Draw(img)
    # next, lets decide what the middle means in terms of coordinates in the image
    x1=location[0]+int(gap_box[2]/2)
    # and our x2 is just the same thing, since this is a one pixel vertical line
    x2=x1
    # our starting y coordinate is just the y coordinate which was passed in, the top of the box
    y1=location[1]
    # but we want our final y coordinate to be the bottom of the box
    y2=y1+gap_box[3]
    drawing_object.rectangle((x1,y1,x2,y2), fill = 'black', outline ='black')
    # 这个函数不需要 return,因为我们改变了这个图

现在,我们把所有的方法合起来,我们迭代整个文本图像的所有像素来确认它是否存在一个 gap,如果存在则在整个 gap_box 中间画一条线,

def process_image(img):
    '''Takes in an image of text and adds black vertical bars to break up columns
    :param img: A PIL.Image file
    :return: A modified PIL.Image file
    '''
    # we'll start with a familiar iteration process
    for x in range(img.width):
        for y in range(img.height):
            # check if there is a gap at this point
            if (gap_check(img, (x,y))):
                # then update image to one which has a separator drawn on it
                draw_sep(img, (x,y))
    # and for good measure we'll return the image we modified
    return img

# Lets read in our test image and convert it through binarization
i=Image.open("readonly/two_col.png").convert("L")
i=process_image(i)
display(i)

#Note: This will take some time to run! Be patient!

image-20200604234503228

一点也不差! 图片底部的效果对我来说有点出乎意料,但这是有道理的。 您可以想象有几种方法可以尝试控制它。 让我们看看通过kraken layout engine运行时新图像的工作方式

display(show_boxes(i))  # i=process_image(i)
image-20200604235714574

看起来非常准确,并且可以解决我们面临的问题。 虽然我们创建的方法确实非常慢,但是如果我们想在较大的文本上使用它,这将是一个问题。 但我想向您展示如何混合使用自己的逻辑并使用正在使用的库。 仅仅因为Kraken不能完美地工作,并不意味着我们不能在其之上构建更具体的用例。
我想暂停一下本讲座,并请您反思一下我们在此处编写的代码。 我们以一些非常简单的库用法开始了本课程,但是现在我们正在更深入地研究并在这些库的帮助下自行解决问题。 在进入最后一个库之前,您认为您准备如何充分利用python技能?

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【Lecture 5.3】Release the Kraken 的相关文章

  • Qt debug版本运行正常release版本运行崩溃问题记录

    问题由来 某一项目debug版本运行正常 进入发布阶段 结果release后的版本出现了崩溃问题 因为是release版本 不能debug运行 只能通过printf debug 虽然问题原因很简单 但是耗费了大量时间 故做此记录 以供后续参
  • buildroot - 如何更改内核版本字符串

    我使用 buildroot 和 Armstrong linux Linux4sam 开发 SAM9G25 EK 板 我的脚本正在创建linux版本 2 6 39 这个 令人困惑 lib modules文件夹等 我想删除它 我发现只有两个文件
  • 团队构建:使用 MSDeploy 本地发布

    我刚刚开始使用团队构建功能 我发现做一些非常简单的事情所需的大量事情有点令人不知所措 我目前的设置是一个包含 Web 应用程序 组装应用程序和测试应用程序的解决方案 Web 应用程序设置了一个通过文件系统发布的 PublishProfile
  • 使用“导出”从 SVN 发布,然后?如何升级?

    使用 SVN 我正在考虑使用 SVN 导出命令将第一个版本 版本 1 0 发布到生产服务器 升级的下一步是什么 例如 如果我想发布下一个版本 1 1 应该如何完成 有没有办法在不删除所有文件的情况下直接从标签升级 一般发布步骤 创建标签来自
  • 在 Gradle 构建中配置多个上传存储库

    我想将我的工件上传到远程 Nexus 存储库 因此我在 Nexus 中配置了快照和发布存储库 部署到两个作品 现在我想配置我的构建 以便我可以决定要部署在哪个存储库中 gradle uploadArchives应该部署到我的快照存储库 gr
  • 如何在 VS Express 2010 中进行发布构建?

    或者我应该复制 bin Release 中除 pdb 之外的所有文件 UPD UPD 2 这就是我在 构建 选项卡的 高级 设置中找到的内容 这是我要找的吗 要在 VS 2010 Express 中进行发布构建 您需要将项目的构建配置从 调
  • Maven 在发布时不会复制未跟踪的资源

    我的问题是关于使用 maven 进行发布时要包含在 jar 文件中的资源 我正在使用 Maven 来构建我的项目 当我运行时 mvn package 资源包含在输出 jar 中 但当我跑步时 mvn release prepare mvn
  • 如何销毁/释放1个活动/布局中使用的资源?

    How do I release resources used in 1 activity So I got 3 layouts and activity for each layout but the problem is when I
  • Visual Studio 2008解决方案发布版本运行时致命错误

    我有一个 Visual Studio 2008 解决方案 其中包含一些项目并使用 dll 及其标头 在调试版本中 它 解决方案 工作得非常好 在发布版本中 它可以成功编译 但是在执行 dll 文件中定义的某些函数时 它会失败 正如我所说 该
  • 其他开发团队如何处理版本号?

    我们的应用程序已经相当成熟 因此我们的版本已经达到了 16 但是 这可能会给人留下该软件已经过时且脱节的印象 有多少商业应用程序的版本为 20 显然 版本号是相当任意的 其他人使用什么 我非常喜欢 Ubuntu 的 Month date 方
  • 如何用Java刷新Android Fragment v1.3.0

    从新的 Fragment 版本 1 3 0 开始 刷新自身内的 Fragment 似乎不像在版本 1 2 5 中那样有效 适用于我的项目 1 2 5 版本的代码 FragmentTransaction ft getFragmentManag
  • 在发布期间标记 TFS Git 存储库

    我正在使用 TFS 2017 设置构建 发布环境 我将构建设置为在每次提交后自动运行 当我们准备好发布应用程序的版本时 手动创建一个发布 然后部署到各种环境 我们希望在 git 存储库中标记已发布的版本 以便轻松了解哪个 git 修订版本对
  • 如何使用 Gradle 创建发布签名的 apk 文件?

    我想让我的 Gradle 构建使用 Gradle 创建一个发布签名的 apk 文件 我不确定代码是否正确或者我在执行时是否缺少参数gradle build 这是我的一些代码build gradle build gradle kts file
  • 发布时的 GitHub Actions 创建的工作流触发器不起作用

    我有一个GitHub 操作 https docs github com en actions在我的存储库的主分支上实现的工作流程 它创建了一个新的release https docs github com en github adminis
  • Android NDK 中的调试与发布构建

    我正在开发一个必须移植到 Android 的大型游戏引擎 所有代码都是 C C 因此我们通过 NDK 进行移植 我已经构建了所有内容 但经过大量搜索后 我仍然不确定构建 so 文件的调试版本与发布版本的最佳方法是什么 每次都用手改变东西已经
  • Proguard 使 Android 应用程序中的 R 类反射不再起作用

    自从我尝试在 Google Play 上发布它以来 我已经在我的项目中激活了 proguard 尽管 proguard android txt 有 keepclassmembers class R public static
  • 本地图像在 React-Native 应用程序发布版本中不可见

    在我的反应本机应用程序中我有 src http postimg org image ak6w7cbk3 文件夹 其中包括Images文件夹和屏幕文件夹 Myscreens文件夹有各种成分我在哪里使用本地图像Images使用以下代码
  • Flutter 发行版 apk 未安装在设备上

    我已经发布了带有签名密钥的 apk 但它没有安装在 Android 设备上 它显示了此消息 apk安装失败 错误 无法解析错误字符串 但调试模式 apk 该应用程序工作正常 释放命令 flutter 构建 apk release 我确实遵循
  • 如何正确使用“mvn release:prepare”?

    我尝试了这个命令 用dryrun在我的 Maven 项目上进行测试 mvn release clean release prepare DdryRun true DgenerateBackupPoms false Dtag solocal
  • Jenkins + Gradle + Artifactory:无法读取生成的构建信息

    我正在尝试使用 Jenkins Pipeline 调用 Gradle 工具 将我的工件推送到 Artifactory 我正在关注 GitHub 上发布的示例 Example1 https github com JFrogDev projec

随机推荐