Python 从扫描图像中快速修剪空白带噪空间的方法
我正在处理扫描文件(身份证、驾照等)。当我对它们进行一些预处理时,我面临的问题是,文档只占据了图像的一小部分,其余的部分都是空白/有噪声的空间。出于这个原因,我想开发一个Python代码,自动地修剪不需要的区域,并且只保留文档所在的区域(而不必预先定义每个文档的分辨率)。使用OpenCV中的Python 从扫描图像中快速修剪空白带噪空间的方法,python,opencv,image-processing,optimization,Python,Opencv,Image Processing,Optimization,我正在处理扫描文件(身份证、驾照等)。当我对它们进行一些预处理时,我面临的问题是,文档只占据了图像的一小部分,其余的部分都是空白/有噪声的空间。出于这个原因,我想开发一个Python代码,自动地修剪不需要的区域,并且只保留文档所在的区域(而不必预先定义每个文档的分辨率)。使用OpenCV中的findContours(),这是可能的。然而,大多数文档(尤其是旧文档)轮廓不清晰,其末端不够清晰,无法检测。另外,在空白空间中的噪声也可以被检测为等高线,所以轮廓对于所有的情况都不起作用。 我的想法是:
findContours()
,这是可能的。然而,大多数文档(尤其是旧文档)轮廓不清晰,其末端不够清晰,无法检测。另外,在空白空间中的噪声也可以被检测为等高线,所以轮廓对于所有的情况都不起作用。
我的想法是:
按位\u not()
函数来分隔
背景来自弗罗格伦import cv2
import numpy as np
def crop(filename):
#Read the image
img = cv2.imread(filename)
#Convert to grayscale
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
#Separate the background from the foreground
bit = cv2.bitwise_not(gray)
#Apply adaptive mean thresholding
amtImage = cv2.adaptiveThreshold(bit, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 35, 15)
#Apply erosion to fill the gaps
kernel = np.ones((15,15),np.uint8)
erosion = cv2.erode(amtImage,kernel,iterations = 2)
#Take the height and width of the image
(height, width) = img.shape[0:2]
#Ignore the limits/extremities of the document (sometimes are black, so they distract the algorithm)
image = erosion[50:height - 50, 50: width - 50]
(nheight, nwidth) = image.shape[0:2]
#Create a list to save the indexes of lines containing more than 20% of black.
index = []
for x in range (0, nheight):
line = []
for y in range(0, nwidth):
line2 = []
if (image[x, y] < 150):
line.append(image[x, y])
if (len(line) / nwidth > 0.2):
index.append(x)
#Create a list to save the indexes of columns containing more than 15% of black.
index2 = []
for a in range(0, nwidth):
line2 = []
for b in range(0, nheight):
if image[b, a] < 150:
line2.append(image[b, a])
if (len(line2) / nheight > 0.15):
index2.append(a)
#Crop the original image according to the max and min of black lines and columns.
img = img[min(index):max(index) + min(250, (height - max(index))* 10 // 11) , max(0, min(index2)): max(index2) + min(250, (width - max(index2)) * 10 // 11)]
#Save the image
cv2.imwrite('res_' + filename, img)
导入cv2
将numpy作为np导入
def裁剪(文件名):
#阅读图片
img=cv2.imread(文件名)
#转换为灰度
灰色=cv2.CVT颜色(img,cv2.COLOR\U BGR2GRAY)
#将背景与前景分开
位=cv2。按位\u非(灰色)
#应用自适应平均阈值
amtImage=cv2.adaptiveThreshold(位,255,cv2.ADAPTIVE_THRESH_MEAN_C,cv2.THRESH_二进制,35,15)
#应用侵蚀来填充间隙
kernel=np.one((15,15),np.uint8)
侵蚀=cv2。侵蚀(amtImage,内核,迭代次数=2)
#获取图像的高度和宽度
(高度、宽度)=图像形状[0:2]
#忽略文档的限制/末端(有时是黑色的,因此会分散算法的注意力)
图像=侵蚀[50:高度-50,50:宽度-50]
(nheight,nwidth)=图像.形状[0:2]
#创建一个列表以保存包含20%以上黑色的行的索引。
索引=[]
对于范围内的x(0,n高):
行=[]
对于范围(0,nwidth)内的y:
第2行=[]
如果(图像[x,y]<150):
行。追加(图像[x,y])
如果(长度(直线)/nwidth>0.2):
index.append(x)
#创建一个列表以保存包含15%以上黑色的列的索引。
index2=[]
对于范围内的(0,nwidth):
第2行=[]
对于范围(0,N)内的b:
如果图像[b,a]<150:
第2行追加(图像[b,a])
如果(len(line2)/n高度>0.15):
index2.append(a)
#根据黑线和黑列的最大值和最小值裁剪原始图像。
img=img[min(index):max(index)+min(250,(高度-max(index))*10//11),max(0,min(index2)):max(index2)+min(250,(宽度-max(index2))*10//11)]
#保存图像
cv2.imwrite('res_uu'+文件名,img)
下面是一个例子:我使用了来自互联网的图像来避免任何保密问题这里需要注意的是,图像质量比我处理的示例要好得多(空白区域不包含噪声)。
输入:1920x1080
输出:801x623
我用不同的文档测试了这段代码,效果很好。问题是,处理单个文档需要花费大量时间(因为循环和读取图像的每个像素两次:一次使用行,第二次使用列)。
是否可以进行一些修改以优化代码并减少处理时间 欢迎提出任何建议。
多谢各位 编辑:
我忘了提到我已经把同一个问题发了进来,但是我没有得到答案。因此,我标记了这个问题,并要求主持人将其迁移到StakOverflow。因为我没有从主持人那里得到答案,所以我决定把它贴在这里,因为我认为它也是关于这个主题的。一旦我在其中一个网站上得到答案,我将删除另一个网站上的问题以避免重复。这是我的方法,请查看:
import cv2
import numpy as np
img = cv2.imread("1.png")
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
#Separate the background from the foreground
bit = cv2.bitwise_not(gray)
nonzero = np.nonzero(bit)
minx = min(nonzero[1])
maxx = max(nonzero[1])
miny = min(nonzero[0])
maxy = max(nonzero[0])
res = img[miny:maxy,minx:maxx].copy()
cv2.rectangle(img,(minx,miny),(maxx,maxy),(0,0,255),2)
cv2.imshow('img',img)
cv2.imshow('bit',bit)
cv2.waitKey(0)
cv2.destroyAllWindows()
这是我的方法,请查看:
import cv2
import numpy as np
img = cv2.imread("1.png")
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
#Separate the background from the foreground
bit = cv2.bitwise_not(gray)
nonzero = np.nonzero(bit)
minx = min(nonzero[1])
maxx = max(nonzero[1])
miny = min(nonzero[0])
maxy = max(nonzero[0])
res = img[miny:maxy,minx:maxx].copy()
cv2.rectangle(img,(minx,miny),(maxx,maxy),(0,0,255),2)
cv2.imshow('img',img)
cv2.imshow('bit',bit)
cv2.waitKey(0)
cv2.destroyAllWindows()
在与交换意见后,我最终找到了一个更优化的解决方案,按照他的建议使用了
findContour
。以下是我结束的代码:
import cv2
import numpy as np
def func(indir, filename, outdir):
img = cv2.imread(indir + filename)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
bit = cv2.bitwise_not(gray)
bit = bit[50:bit.shape[0] -50, 50:bit.shape[1] - 50]
amtImage = cv2.adaptiveThreshold(bit, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 35, 15)
kernel = np.ones((5,5),np.uint8)
dilation = cv2.dilate(amtImage,kernel,iterations = 2)
kernel = np.ones((25,25),np.uint8)
erosion = cv2.erode(dilation, kernel, iterations = 10)
bit = cv2.bitwise_not(erosion)
_, contours, hierarchy = cv2.findContours(bit, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
if (contours != 0):
c = max(contours, key = cv2.contourArea)
x,y,w,h = cv2.boundingRect(c)
print(x, y, w, h)
final = img[max(0, (y - 50)):(y + h) + min(250, (img.shape[0] - (y + h)) * 10 // 11), max(0, (x - 50)):(x + w) + min(250, (img.shape[1] - (x + w)) * 10 // 11)]
cv2.imwrite(outdir + filename, final)
在这段代码中,我没有义务遍历图像的每个像素,也没有义务保留索引列表。所以速度要快得多我确信这段代码可以进行更多优化,这就是为什么我不接受我的答案。
谢谢大家。在与交换意见后,我最终找到了一个更优化的解决方案,在该解决方案中,我按照他的建议使用了
findContour
。以下是我结束的代码:
import cv2
import numpy as np
def func(indir, filename, outdir):
img = cv2.imread(indir + filename)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
bit = cv2.bitwise_not(gray)
bit = bit[50:bit.shape[0] -50, 50:bit.shape[1] - 50]
amtImage = cv2.adaptiveThreshold(bit, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 35, 15)
kernel = np.ones((5,5),np.uint8)
dilation = cv2.dilate(amtImage,kernel,iterations = 2)
kernel = np.ones((25,25),np.uint8)
erosion = cv2.erode(dilation, kernel, iterations = 10)
bit = cv2.bitwise_not(erosion)
_, contours, hierarchy = cv2.findContours(bit, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
if (contours != 0):
c = max(contours, key = cv2.contourArea)
x,y,w,h = cv2.boundingRect(c)
print(x, y, w, h)
final = img[max(0, (y - 50)):(y + h) + min(250, (img.shape[0] - (y + h)) * 10 // 11), max(0, (x - 50)):(x + w) + min(250, (img.shape[1] - (x + w)) * 10 // 11)]
cv2.imwrite(outdir + filename, final)
在这段代码中,我没有义务遍历图像的每个像素,也没有义务保留索引列表。所以速度要快得多我确信这段代码可以进行更多优化,这就是为什么我不接受我的答案。
谢谢大家。您可以只保留最小和最大索引,而不是保留索引列表。这会快得多。我会改变这一点,谢谢。这个问题在代码复查堆栈交换中得到了答案。通过,您可以只保留最小和最大索引,而不是保留索引列表。那会快得多。我会改的,谢谢。这个问题在代码复查堆栈交换中有答案