Python 查找图像内的边(矩形边框)

Python 查找图像内的边(矩形边框),python,imagemagick,image-manipulation,edge-detection,Python,Imagemagick,Image Manipulation,Edge Detection,我在背景上有一张便笺的图像(比如墙壁或笔记本电脑),我想检测便笺的边缘(粗略检测也可以),这样我就可以在上面进行裁剪 我计划使用ImageMagick进行实际裁剪,但我一直在检测边缘 理想情况下,我的输出应该为4个边界点提供4个坐标,以便我可以在其上运行裁剪 我应该如何进行这项工作 您可以使用ImageMagick实现这一点 人们可以想出不同的即时通讯方法。这是我想到的第一个算法。它假设“便笺”在较大的图像上没有倾斜或旋转: 第一阶段:使用canny边缘检测来显示便笺的边缘 第二阶段:确定边的坐

我在背景上有一张便笺的图像(比如墙壁或笔记本电脑),我想检测便笺的边缘(粗略检测也可以),这样我就可以在上面进行裁剪

我计划使用ImageMagick进行实际裁剪,但我一直在检测边缘

理想情况下,我的输出应该为4个边界点提供4个坐标,以便我可以在其上运行裁剪

我应该如何进行这项工作


您可以使用ImageMagick实现这一点

人们可以想出不同的即时通讯方法。这是我想到的第一个算法。它假设“便笺”在较大的图像上没有倾斜或旋转:

  • 第一阶段:使用canny边缘检测来显示便笺的边缘
  • 第二阶段:确定边的坐标
  • Canny边缘检测 此命令将创建一个黑白图像,描绘原始图像中的所有边:

    convert                              \
      http://i.stack.imgur.com/SxrwG.png \
     -canny 0x1+10%+30%                  \
      canny-edges.png
    

    确定边的坐标 假设图像大小为
    XxY
    像素。然后可以将图像调整为
    1xY
    列和
    Xx1
    行像素,其中每个像素的颜色值是与相应列/行像素位于同一行或同一列的所有像素的相应像素的平均值

    下面是一个示例,我将首先将新的canny-edges.png调整为
    4xY
    Xx4
    图像:

    identify -format  " %W x %H\n"  canny-edges.png
     400x300
    
    convert canny-edges.png -resize 400x4\!   canny-4cols.png
    convert canny-edges.png -resize   4x300\! canny-4rows.png
    
    canny-4cols.png

    canny-4rows.png

    现在,前面的图像已经可视化了将图像压缩到几列或几行像素中所能达到的效果,让我们用一列和一行来实现。同时,我们将输出格式更改为文本,而不是PNG,以便获得这些白色像素的坐标:

    convert canny-edges.png -resize 400x1\!   canny-1col.txt
    convert canny-edges.png -resize   1x300\! canny-1row.txt
    
    以下是canny-1col.txt的部分输出:

    # ImageMagick pixel enumeration: 400,1,255,gray
    0,0: (0,0,0)  #000000  gray(0)
    1,0: (0,0,0)  #000000  gray(0)
    2,0: (0,0,0)  #000000  gray(0)
    [....]
    73,0: (0,0,0)  #000000  gray(0)
    74,0: (0,0,0)  #000000  gray(0)
    75,0: (10,10,10)  #0A0A0A  gray(10)
    76,0: (159,159,159)  #9F9F9F  gray(159)
    77,0: (21,21,21)  #151515  gray(21)
    78,0: (156,156,156)  #9C9C9C  gray(156)
    79,0: (14,14,14)  #0E0E0E  gray(14)
    80,0: (3,3,3)  #030303  gray(3)
    81,0: (3,3,3)  #030303  gray(3)
    [....]
    162,0: (3,3,3)  #030303  gray(3)
    163,0: (4,4,4)  #040404  gray(4)
    164,0: (10,10,10)  #0A0A0A  gray(10)
    165,0: (7,7,7)  #070707  gray(7)
    166,0: (8,8,8)  #080808  gray(8)
    167,0: (8,8,8)  #080808  gray(8)
    168,0: (8,8,8)  #080808  gray(8)
    169,0: (9,9,9)  #090909  gray(9)
    170,0: (7,7,7)  #070707  gray(7)
    171,0: (10,10,10)  #0A0A0A  gray(10)
    172,0: (5,5,5)  #050505  gray(5)
    173,0: (13,13,13)  #0D0D0D  gray(13)
    174,0: (6,6,6)  #060606  gray(6)
    175,0: (10,10,10)  #0A0A0A  gray(10)
    176,0: (10,10,10)  #0A0A0A  gray(10)
    177,0: (7,7,7)  #070707  gray(7)
    178,0: (8,8,8)  #080808  gray(8)
    [....]
    319,0: (3,3,3)  #030303  gray(3)
    320,0: (3,3,3)  #030303  gray(3)
    321,0: (14,14,14)  #0E0E0E  gray(14)
    322,0: (156,156,156)  #9C9C9C  gray(156)
    323,0: (21,21,21)  #151515  gray(21)
    324,0: (159,159,159)  #9F9F9F  gray(159)
    325,0: (10,10,10)  #0A0A0A  gray(10)
    326,0: (0,0,0)  #000000  gray(0)
    327,0: (0,0,0)  #000000  gray(0)
    [....]
    397,0: (0,0,0)  #000000  gray(0)
    398,0: (0,0,0)  #000000  gray(0)
    399,0: (0,0,0)  #000000  gray(0)
    
    如您所见,从文本中检测到的边缘也会影响像素的灰度值。因此,我们可以在命令中引入额外的
    -threshold 50%
    操作,以获得纯黑白输出:

    convert canny-edges.png -resize 400x1\!   -threshold 50% canny-1col.txt
    convert canny-edges.png -resize   1x300\! -threshold 50% canny-1row.txt
    
    我不会在这里引用新文本文件的内容,如果您感兴趣,您可以尝试一下自己查找。相反,我将执行一个快捷方式:将像素颜色值的文本表示输出到
    ,并直接为所有非黑色像素grep它:

    convert canny-edges.png -resize 400x1\!   -threshold 50% txt:- \
    | grep -v black
    
      # ImageMagick pixel enumeration: 400,1,255,srgb
      76,0: (255,255,255)  #FFFFFF  white
      78,0: (255,255,255)  #FFFFFF  white
      322,0: (255,255,255)  #FFFFFF  white
      324,0: (255,255,255)  #FFFFFF  white
    
    convert canny-edges.png -resize   1x300\! -threshold 50% txt:- \
    | grep -v black
    
      # ImageMagick pixel enumeration: 1,300,255,srgb
      0,39: (255,255,255)  #FFFFFF  white
      0,41: (255,255,255)  #FFFFFF  white
      0,229: (255,255,255)  #FFFFFF  white
      0,231: (255,255,255)  #FFFFFF  white
    
    根据以上结果,您可以得出以下结论:图像的四个像素坐标 另一幅图像中的粘贴说明包括:

  • 左下角:
    (323 | 40)
  • 右上角:
    (77 | 230)
  • 该区域的宽度为246像素,高度为190像素

    (ImageMagick假定其坐标系的原点为图像的左上角。)

    要现在从原始图像中剪切便笺,可以执行以下操作:

    convert http://i.stack.imgur.com/SxrwG.png[246x190+77+40] sticky-note.png
    

    更多可供探索的选项
    autotrace
    通过将中间的“canny edges.png”转换为SVG矢量图形,例如通过运行
    autotrace
    ,您可以进一步简化上述过程(如果需要,甚至可以将其转换为自动工作脚本)

    如果您的便笺倾斜或旋转,这可能很有用

    霍夫线检测 一旦有了“canny”线,您还可以对其应用Hough线检测算法:

    convert              \
      canny-edges.png    \
     -background black   \
     -stroke red         \
     -hough-lines 5x5+20 \
      lines.png
    

    请注意,
    -hough lines
    操作符将检测到的线从原始图像的一条边(带有浮点值)延伸并绘制到另一条边

    上一个命令最终将这些线转换为PNG,而
    -hough lines
    操作符实际上会在内部生成一个文件(Magick矢量图形)。这意味着您可以实际读取MVG文件的源代码,并确定“红线”图像中描述的每一行的数学参数:

    这更复杂,也适用于不严格水平和/或垂直的边缘

    但是您的示例图像确实使用了水平和垂直边,因此您甚至可以使用简单的shell命令来发现这些边

    生成的MVG文件中总共有80行描述。您可以识别该文件中的所有水平线:

    cat lines.mvg                              \
     | while read a b c d e ; do               \
         if [ x${b/0,/} == x${c/400,/} ]; then \
           echo "$a    $b    $c   $d    $e" ;  \
         fi;                                   \
       done
    
        line     0,39.5    400,39.5    # 249
        line     0,62.5    400,62.5    # 48
        line     0,71.5    400,71.5    # 52
        line     0,231.5   400,231.5   # 249
    
    现在识别所有垂直线

    cat lines.mvg                              \
     | while read a b c d e; do                \
         if [ x${b/,0/} == x${c/,300} ]; then  \
            echo "$a    $b    $c   $d    $e" ; \
         fi;                                   \
       done
    
       line     76.5,0   76.5,300     # 193
       line    324.5,0  324.5,300     # 193
    

    上周我遇到了检测图像边界(空白)的类似问题,花了很多时间尝试各种方法和工具,之后最终使用熵差计算方法解决了这个问题,所以这里是JFYI算法

    假设要检测200x100px图像的顶部是否有边框:

  • 获取图像上部25%高度(25px)(0:25,0:200)
  • 从上片端开始,以相同的高度将下片移到图像中心(25:50,0:200)
  • 计算两个物体的熵
  • 找到熵差并将其与当前块高度一起存储
  • 使上片减少1px(24 px),从第2页开始重复,直到我们到达图像边缘(高度0)-每次迭代调整扫描区域的大小,从而滑动到图像边缘
  • 查找存储的最大熵差及其块高度-如果它更靠近边缘而不是图像中心,则这是边界的中心,并且最大熵差高于预设阈值(例如0.5)
  • 并将此算法应用于图像的每一面

    下面是一段代码,用于检测图像是否有上边框,并找到其近似坐标(从顶部偏移),将灰度('L'模式)枕头图像传递给扫描功能:

    import numpy as np
    
    
    MEDIAN = 0.5
    
    
    def scan(im):
        w, h = im.size
        array = np.array(im)
    
        center_ = None
        diff_ = None
        for center in reversed(range(1, h // 4 + 1)):
            upper = entropy(array[0: center, 0: w].flatten())
            lower = entropy(array[center: 2 * center, 0: w].flatten())
            diff = upper / lower if lower != 0.0 else MEDIAN
            if center_ is None or diff_ is None:
                center_ = center
                diff_ = diff
            if diff < diff_:
                center_ = center
                diff_ = diff
        top = diff_ < MEDIAN and center_ < h // 4, center_, diff_
    
    将numpy导入为np
    中位数=0.5
    def扫描(im):
    w、 h=im尺寸
    数组=np.数组(im)
    中心=无
    差异=无
    对于反向中心(范围(1,h//4+1)):
    上限=熵(数组[0:cent
    
    import numpy as np
    
    
    MEDIAN = 0.5
    
    
    def scan(im):
        w, h = im.size
        array = np.array(im)
    
        center_ = None
        diff_ = None
        for center in reversed(range(1, h // 4 + 1)):
            upper = entropy(array[0: center, 0: w].flatten())
            lower = entropy(array[center: 2 * center, 0: w].flatten())
            diff = upper / lower if lower != 0.0 else MEDIAN
            if center_ is None or diff_ is None:
                center_ = center
                diff_ = diff
            if diff < diff_:
                center_ = center
                diff_ = diff
        top = diff_ < MEDIAN and center_ < h // 4, center_, diff_