Более быстрый способ обрезки пустого зашумленного пространства отсканированных изображений - PullRequest
0 голосов
/ 30 января 2019

Я работаю с отсканированными документами (удостоверение личности, водительские права, ...).Проблема, с которой я столкнулся, применяя к ним некоторую предварительную обработку, заключается в том, что документы занимают лишь небольшую область изображения, а все остальное - пустое / зашумленное пространство.По этой причине я хотел разработать код Python, который автоматически обрезает нежелательную область и сохраняет только зону, в которой находится документ ( без предварительного определения разрешения для каждого документа ).Ну, это возможно с использованием findContours() из OpenCV.Однако большинство документов (особенно старых) имеют нечеткий контур, а их конечности недостаточно четкие, чтобы их можно было обнаружить.Кроме того, шум в пустом пространстве также может быть обнаружен как контуры. Таким образом, контуры не будут работать во всех случаях.
Идея, которая пришла мне в голову:

  1. Считайте изображение и конвертируйтев оттенках серого.
  2. Примените функцию bitwise_not() из OpenCV, чтобы отделить фон от вспениваемого грунта.
  3. Примените адаптивное среднее пороговое значение, чтобы устранить как можно больше шума (и, в конечном итоге, отбелитьфон).

На этом уровне у меня фон почти белый, а документ черный, но с белыми пробелами.

Итак, я применил эрозию, чтобы заполнить пробелы в части документа. Прочитайте каждую строку изображения и, если 20% его содержит черный, сохраните его, если он белый, удалите его.проделайте то же самое с каждым столбцом изображения. Обрежьте изображение в соответствии с минимальным и максимальным индексом черных линий и столбцов.

Вот мой код с некоторымикомментарии:

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)

Вот пример: Я использовал изображение из Интернета, чтобы избежать проблем с конфиденциальностью
Здесь следует отметить, что качество изображения намного лучше (Пустое пространство не содержит шума), чем примеры, над которыми я работаю.
ВХОД: 1920x1080
Input
ВЫХОД: 801x623
Output

Я тестировал этот код с различными документами, и он хорошо работает.Проблема в том, что обработка одного документа занимает много времени (из-за циклов и чтения каждого пикселя изображения дважды: один раз со строками, а второй со столбцами).
Можно ли внести некоторые изменения воптимизировать код и сократить время обработки?

Любые предложения более чем приветствуются.
Спасибо.

РЕДАКТИРОВАТЬ:
Я забыл упомянуть, что я уже отправил тот же вопросв Проверка кода Stack Exchange , но я не получил ответа.Поэтому я пометил вопрос и попросил модераторов перенести его в StakOverflow.И так как я не получил ответ от модераторов, я решил опубликовать его здесь, потому что я думаю, что это тоже по теме здесь.Как только я получу ответ на одном из веб-сайтов, я удалю свой вопрос на другом веб-сайте, чтобы избежать избыточности.

Ответы [ 2 ]

0 голосов
/ 30 января 2019

После обмена комментариями с @ Ха Бом я остановился на более оптимизированном решении, в котором я использовал 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)

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

0 голосов
/ 30 января 2019

Вот мой метод, проверьте его:

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()

enter image description here

enter image description here

enter image description here

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...