В этой статье были объяснены шаги по созданию сканера документов с использованием Python и OpenCV с нуля. Интересной особенностью этой статьи является то, что для обнаружения границ использовался Houghlines. Популярный метод контуров имеет проблемы с производительностью с плохо освещенными изображениями с зашумленными границами, поэтому это альтернативный метод, с которым вы можете работать.

Ниже приводится пошаговое объяснение всего процесса.

  1. Импортировать все необходимые библиотеки
from skimage.filters import threshold_local
import numpy as np
import cv2 as cv

2. Считайте изображение и обработайте изображение для обнаружения краев

Изображение преобразуется в оттенки серого, и для сглаживания изображения выполняется размытие. Для удаления нежелательных элементов (помогает в обнаружении краев) было выполнено морфологическое расширение и эрозия. Затем для поиска краев в изображении используется точное обнаружение краев.

img=cv.imread("document.png")
gray=cv.cvtColor(img,cv.COLOR_BGR2GRAY)
kernel = np.ones((5,5),np.uint8)
dilation = cv.dilate(gray,kernel,iterations =5)
blur =cv.GaussianBlur(dilation,(3,3),0)
blur= cv.erode(blur,kernel,iterations =5)
edge=cv.Canny(blur,100,200)

3. Использование Houghlines для поиска краев документа на изображении после точного определения краев

Houghlines использовался для поиска первых n (здесь 8) строк на изображении после точного определения краев. Первоначально был выбран высокий порог (здесь 300), который итеративно понижается, чтобы получить необходимое количество Houghlines. Избыточные строки (строки имеют одинаковые значения rho и theta) удаляются и выбираются четыре верхние строки (соответствующие четырем краям).

t=300;j=0
while(j<8 and t>0):     
    try:linesP=cv.HoughLines(edge,1,np.pi/180,t);j=linesP.shape[0]
    except:j=0
    t=t-10
lines=linesP.reshape(linesP.shape[0],2)
t=0;c=0;lu=[]
for l in lines:
    c=c+1;rho,theta=l
    for lt in lines[c:]:
        t=0
        if(lt[0]!=l[0]):
            rhot,thetat=lt;k=abs(lt-l)<[50,0.5] 
            if(k[0] and k[1]):
                t=-1;break                
    lu.append(l)
lr=np.asarray(lu[:4]);j=np.reshape(lr,[lr.shape[0],1,2])

4. Найдите пересечение четырех наиболее вероятных краев, чтобы получить угловые точки документа.

Найдены пересечения ребер, соответствующих перпендикулярным сторонам границы, и это будут четыре угла прямоугольника.

def l_inter(line1, line2):
    r1, t1 = line1;r2,t2 = line2
    A= np.array([[np.cos(t1),np.sin(t1)],[np.cos(t2),np.sin(t2)]])
    b= np.array([[r1],[r2]]);x0,y0=(0,0)
    if(abs(t1-t2)>1.3):
        return [[np.round(np.linalg.solve(A, b))]]
def points_inter(lines):
    intersections = []
    for i, g in enumerate(lines[:-1]):
        for g2 in lines[i+1:]:
            for line1 in g:
                for line2 in g2:
                    if(l_inter(line1, line2)):
                        intersections.append(l_inter(line1, line2)) 
    return intersections
p=np.asarray(points_inter(j)).reshape(4,2)

5. Чтобы сгладить документ, мы используем методы перспективного преобразования и функции деформации перспективы.

Максимальная и минимальная сумма координат точек соответствуют нижнему правому и верхнему левому углам соответственно. и мин. Разница дает верхний правый и нижний левый углы, которые затем используются для определения ширины и высоты отсканированного документа для матрицы преобразования.

r= np.zeros((4,2), dtype="float32")
s = np.sum(p, axis=1);r[0] = p[np.argmin(s)];r[2] = p[np.argmax(s)]
d = np.diff(p, axis=1);r[1] = p[np.argmin(d)];r[3] = p[np.argmax(d)]
(tl, tr, br, bl) =r
wA = np.sqrt((tl[0]-tr[0])**2 + (tl[1]-tr[1])**2 )
wB = np.sqrt((bl[0]-br[0])**2 + (bl[1]-br[1])**2 )
maxW = max(int(wA), int(wB))
hA = np.sqrt((tl[0]-bl[0])**2 + (tl[1]-bl[1])**2 )
hB = np.sqrt((tr[0]-br[0])**2 + (tr[1]-br[1])**2 )
maxH = max(int(hA), int(hB))
ds= np.array([[0,0],[maxW-1, 0],[maxW-1, maxH-1],[0, maxH-1]], dtype="float32")
transformMatrix = cv.getPerspectiveTransform(r,ds)
scan = cv.warpPerspective(gray, transformMatrix, (maxW, maxH))

6. Изображение преобразуется в черно-белое и сохраняется в системе.

Бинаризация выполняется для преобразования отсканированного документа в черно-белый посредством установления пороговых значений. Значения порога и смещения могут быть установлены в соответствии с вашими требованиями.

T = threshold_local(scan,21, offset=10, method="gaussian")
scanBW = (scan > T).astype("uint8")* 255
cv.imwrite("Scan.png",scanBW)

Примечание.

Различные параметры, используемые в данном примере, были выбраны исходя из их эффективности в данном случае. Возможно, вам придется попробовать другой набор параметров для вашего случая.

Вы можете увидеть полный исходный код на https://github.com/Joel144/Document_scanner