查找图像内部的边缘(矩形的边界)
Find edges (border of rectangle) inside an image
我在背景(比如墙壁或笔记本电脑)上有一张便利贴的图像,我想检测便利贴的边缘(粗略检测也可以),这样我就可以 运行 上面的作物。
我计划使用 ImageMagick 进行实际裁剪,但在检测边缘时卡住了。
理想情况下,我的输出应该为 4 个边界点提供 4 个坐标,这样我就可以 运行 在上面进行裁剪。
我该如何处理?
您可以使用 ImageMagick 做到这一点。
人们可以想出不同的 IM 方法。这是我想到的第一个算法。它假设 "sticky notes" 在大图上没有倾斜或旋转:
- 第一阶段:使用canny边缘检测来揭示便利贴的边缘。
- 第二阶段:确定边的坐标。
Canny 边缘检测
此命令将创建描绘原始图像中所有边缘的黑白图像:
convert \
http://i.stack.imgur.com/SxrwG.png \
-canny 0x1+10%+30% \
canny-edges.png
确定边的坐标
假设图像大小为 XxY
像素。然后,您可以将图像调整为 1xY
列和 Xx1
行像素,其中每个像素的颜色值是同一行或同一列中所有像素的相应像素的平均值相应的 column/row 像素。
作为下面可以看到的示例,我首先将新的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
现在前面的图像可视化了将图像压缩调整为几列或几行像素将实现的效果,让我们用单列和单行来实现。同时我们将输出格式更改为text,而不是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
这里就不引用新的文本文件的内容了,有兴趣的可以自己试试看。相反,我会做一个快捷方式:我会将像素颜色值的文本表示输出到 <stdout>
并直接对所有非黑色像素进行 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" 行后,您还可以对它们应用霍夫线检测算法:
convert \
canny-edges.png \
-background black \
-stroke red \
-hough-lines 5x5+20 \
lines.png
请注意,-hough-lines
运算符将检测到的线条从一个边缘(具有浮点值)扩展并绘制到原始图像的另一边缘。
虽然前面的命令最终将行转换为 PNG,但 -hough-lines
运算符实际上生成了 MVG 文件 (Magick Vector Graphics) 内部。这意味着您实际上可以阅读 MVG 文件的源代码,并确定 "red lines" 图像中描述的每一行的数学参数:
convert \
canny-edges.png \
-hough-lines 5x5+20 \
lines.mvg
这更复杂,也适用于非严格水平 and/or 垂直的边缘。
但是您的示例图像 使用水平和垂直边缘,因此您甚至可以使用简单的 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是算法。
假设您要检测 200x100 像素的图片顶部是否有边框:
- 获取上图25%高度(25px)(0:25,0:200)
- 从上片末端开始,深入到图像中心,得到相同高度的下片(25:50, 0:200)
upper and lower pieces depicted
- 计算两部分的熵
- 求熵差并与当前区块高度一起存储
- 使上面的部分少 1px (24 px) 并从 p.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_
带边框和清晰(无边框)图像处理示例的完整源代码在此处:https://github.com/embali/enimda/
我在背景(比如墙壁或笔记本电脑)上有一张便利贴的图像,我想检测便利贴的边缘(粗略检测也可以),这样我就可以 运行 上面的作物。
我计划使用 ImageMagick 进行实际裁剪,但在检测边缘时卡住了。
理想情况下,我的输出应该为 4 个边界点提供 4 个坐标,这样我就可以 运行 在上面进行裁剪。
我该如何处理?
您可以使用 ImageMagick 做到这一点。
人们可以想出不同的 IM 方法。这是我想到的第一个算法。它假设 "sticky notes" 在大图上没有倾斜或旋转:
- 第一阶段:使用canny边缘检测来揭示便利贴的边缘。
- 第二阶段:确定边的坐标。
Canny 边缘检测
此命令将创建描绘原始图像中所有边缘的黑白图像:
convert \
http://i.stack.imgur.com/SxrwG.png \
-canny 0x1+10%+30% \
canny-edges.png
确定边的坐标
假设图像大小为 XxY
像素。然后,您可以将图像调整为 1xY
列和 Xx1
行像素,其中每个像素的颜色值是同一行或同一列中所有像素的相应像素的平均值相应的 column/row 像素。
作为下面可以看到的示例,我首先将新的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
现在前面的图像可视化了将图像压缩调整为几列或几行像素将实现的效果,让我们用单列和单行来实现。同时我们将输出格式更改为text,而不是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
这里就不引用新的文本文件的内容了,有兴趣的可以自己试试看。相反,我会做一个快捷方式:我会将像素颜色值的文本表示输出到 <stdout>
并直接对所有非黑色像素进行 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" 行后,您还可以对它们应用霍夫线检测算法:
convert \
canny-edges.png \
-background black \
-stroke red \
-hough-lines 5x5+20 \
lines.png
请注意,-hough-lines
运算符将检测到的线条从一个边缘(具有浮点值)扩展并绘制到原始图像的另一边缘。
虽然前面的命令最终将行转换为 PNG,但 -hough-lines
运算符实际上生成了 MVG 文件 (Magick Vector Graphics) 内部。这意味着您实际上可以阅读 MVG 文件的源代码,并确定 "red lines" 图像中描述的每一行的数学参数:
convert \
canny-edges.png \
-hough-lines 5x5+20 \
lines.mvg
这更复杂,也适用于非严格水平 and/or 垂直的边缘。
但是您的示例图像 使用水平和垂直边缘,因此您甚至可以使用简单的 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是算法。
假设您要检测 200x100 像素的图片顶部是否有边框:
- 获取上图25%高度(25px)(0:25,0:200)
- 从上片末端开始,深入到图像中心,得到相同高度的下片(25:50, 0:200)
upper and lower pieces depicted
- 计算两部分的熵
- 求熵差并与当前区块高度一起存储
- 使上面的部分少 1px (24 px) 并从 p.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_
带边框和清晰(无边框)图像处理示例的完整源代码在此处:https://github.com/embali/enimda/