如何从 OpenCV 中的图像中删除空格?
Posted
技术标签:
【中文标题】如何从 OpenCV 中的图像中删除空格?【英文标题】:How to remove whitespace from an image in OpenCV? 【发布时间】:2018-09-29 03:34:16 【问题描述】:我有以下图像,其中包含文本和文本下方的大量空白。我想裁剪空白区域,使其看起来像第二张图像。
裁剪图像
这就是我所做的
>>> img = cv2.imread("pg13_gau.jpg.png")
>>> gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
>>> edged = cv2.Canny(gray, 30,300)
>>> (img,cnts, _) = cv2.findContours(edged.copy(), cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
>>> cnts = sorted(cnts, key = cv2.contourArea, reverse = True)[:10]
【问题讨论】:
只裁剪boundingRect
的黑色像素
boundingRect
采用轮廓。我应该使用findContour
找到轮廓吗?如何获得左上角黑色像素和右下角黑色像素?
boundingRect 采用点向量...无需查找轮廓。 C++ 代码将只是:std::vector<cv::Point> pts; cv::findNonZero(~gray); cv::Rect roi = cv::boundingRect(pts); cv::Mat1b crop = img(roi);
Python 不会有太大不同
从HERE查看第7节cv2.boundingRect()
我执行了以下操作,但生成的图像不是裁剪后的图像:img = cv2.imread("ws.png"); gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY); coords = cv2.findNonZero(gray); rekt = cv2.boundingRect(coords) cv2.imwrite("rekt.png",rekt)
【参考方案1】:
正如许多人在 cmets 中提到的那样,最好的方法是反转图像,使黑色文本变为白色,找到图像中的所有非零点,然后确定最小跨度边界框是多少。您可以使用此边界框最终裁剪图像。查找轮廓非常昂贵,在这里不需要 - 特别是因为您的文本是轴对齐的。您可以结合使用cv2.findNonZero
和cv2.boundingRect
来满足您的需求。
因此,这样的事情会起作用:
import numpy as np
import cv2
img = cv2.imread('ws.png') # Read in the image and convert to grayscale
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
gray = 255*(gray < 128).astype(np.uint8) # To invert the text to white
coords = cv2.findNonZero(gray) # Find all non-zero points (text)
x, y, w, h = cv2.boundingRect(coords) # Find minimum spanning bounding box
rect = img[y:y+h, x:x+w] # Crop the image - note we do this on the original image
cv2.imshow("Cropped", rect) # Show it
cv2.waitKey(0)
cv2.destroyAllWindows()
cv2.imwrite("rect.png", rect) # Save the image
上面的代码正是我一开始所说的。我们在图像中读取,但我们也转换为灰度,因为您的图像由于某种原因是彩色的。棘手的部分是第三行代码,我将阈值设置为低于 128 的强度,以便深色文本变为白色。然而,这会产生一个二进制图像,所以我转换为uint8
,然后按 255 缩放。这实质上是反转文本。
接下来,给定这张图片,我们找到所有带有cv2.findNonZero
的非零坐标,最后我们将其放入cv2.boundingRect
,这将为您提供边界框的左上角以及宽度和高度。我们终于可以使用它来裁剪图像了。请注意,我们在原始图像而不是倒置图像上执行此操作。我们简单地使用 NumPy 数组索引来为我们进行裁剪。
最后,我们显示图像以表明它可以工作,并将其保存到磁盘。
我现在得到了这张图片:
对于第二张图片,一个好的做法是去掉一些右边框和下边框。我们可以通过首先将图像裁剪到该图像来做到这一点。接下来,该图像包含一些非常小的噪声像素。我建议使用非常小的内核进行形态学打开,然后重做我们上面讨论的逻辑。
因此:
import numpy as np
import cv2
img = cv2.imread('pg13_gau_preview.png') # Read in the image and convert to grayscale
img = img[:-20,:-20] # Perform pre-cropping
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
gray = 255*(gray < 128).astype(np.uint8) # To invert the text to white
gray = cv2.morphologyEx(gray, cv2.MORPH_OPEN, np.ones((2, 2), dtype=np.uint8)) # Perform noise filtering
coords = cv2.findNonZero(gray) # Find all non-zero points (text)
x, y, w, h = cv2.boundingRect(coords) # Find minimum spanning bounding box
rect = img[y:y+h, x:x+w] # Crop the image - note we do this on the original image
cv2.imshow("Cropped", rect) # Show it
cv2.waitKey(0)
cv2.destroyAllWindows()
cv2.imwrite("rect.png", rect) # Save the image
注意:输出图片因隐私原因被删除
【讨论】:
@Anthony 您必须先删除一些右边框以及一些沿臀部的像素,然后才能使用。如果存在像您在图像右侧看到的那样的杂散斑点,则该代码不起作用。在选择边界框之前先尝试对图像执行此操作:img = img[:-20,:-20]
。这将删除行和列的最后 20 个像素。
啊,我明白了,因为那时图像中会有两部分黑色。有没有办法以编程方式删除它,以便裁剪按需要进行?
@Anthony 是的,你绝对可以去除边框像素。在 MATLAB 中,有一个名为 imclearborder
的函数可以删除任何连接到边框的像素。 OpenCV 没有这个功能,但我在之前提供的答案中自己写了一个:***.com/questions/24731810/…。请注意,这可能需要大量计算,因为该过程是查找图像中的所有轮廓,然后检查是否有任何轮廓接触图像中的任何边界像素。然后我将这些轮廓的区域涂成黑色。
@DanMašek 我知道。我现在对此无能为力,但第二张图片的底部有一个非常小的黑点。如果我们在反转后找到所有非零像素,则将包含这个黑点,因此包含这组点的边界框将拉伸以包含这个嘈杂的黑点。我只有在下载并在 GIMP 上查看后才能看到它。在进行裁剪之前,我用形态学删除了它。
您可以将其标记为版主将其删除,或直接向 Imgur 发送 removal request。【参考方案2】:
Opencv 将图像读取为 numpy 数组,直接使用 numpy 会简单得多(scikit-image
也是如此)。一种可能的方法是将图像读取为灰度或转换为灰度图像,然后执行逐行和逐列操作,如下面的代码 sn-p 所示。当所有像素为pixel_value
(本例中为白色)时,这将删除列和行。
def crop_image(filename, pixel_value=255):
gray = cv2.imread(filename, cv2.IMREAD_GRAYSCALE)
crop_rows = gray[~np.all(gray == pixel_value, axis=1), :]
cropped_image = crop_rows[:, ~np.all(crop_rows == pixel_value, axis=0)]
return cropped_image
和输出:
【讨论】:
【参考方案3】:这也可以:
from PIL import Image, ImageChops
img = Image.open("pUq4x.png")
pixels = img.load()
print (f"original: img.size[0] x img.size[1]")
xlist = []
ylist = []
for y in range(0, img.size[1]):
for x in range(0, img.size[0]):
if pixels[x, y] != (255, 255, 255, 255):
xlist.append(x)
ylist.append(y)
left = min(xlist)
right = max(xlist)
top = min(ylist)
bottom = max(ylist)
img = img.crop((left-10, top-10, right+10, bottom+10))
img.show()
【讨论】:
不是最优的,可以在for循环里面保存一个max和min的记录。 能否将数组转换为 NumPy 并使用np.min
和 np.max
?在 Python 中用于图像处理的双 for
循环不是首选。以上是关于如何从 OpenCV 中的图像中删除空格?的主要内容,如果未能解决你的问题,请参考以下文章