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

Я сделал это фото со своей камеры:

Шаг 1. Прочтите изображение с помощью opencv. Здесь original.jpg относится к моей фотографии с камеры.

frame = cv2.imread('original.jpg')

Шаг 2. Преобразуйте это изображение в изображение в оттенках серого.

gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
cv2.imwrite('gray' , gray)

Шаг 3. Используйте адаптивную пороговую обработку

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

В Adaptive Threshold есть 3 аргумента:

  • Адаптивный метод - определяет способ расчета порогового значения.
  • Размер блока - определяет размер района.
  • C - это просто константа, которая вычитается из рассчитанного среднего или средневзвешенного значения.
def AdaptiveThresholding(gray):
 gray=cv2.adaptiveThreshold(gray,200,cv2.ADAPTIVE_THRESH_GAUSSIAN_C,cv2.THRESH_BINARY_INV,11,3)
 return gray

В этом случае мы использовали cv2.ADAPTIVE_THRESH_GAUSSIAN_C как адаптивный метод, где пороговое значение представляет собой взвешенную сумму значений соседства, где веса являются окном Гаусса.

Здесь мы взяли Размер блока = 11 и C = 3.

После этой операции Результат будет иметь следующий вид:

Шаг 4: Открытие операции

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

Внимательно изучив изображение адаптивного порога, мы можем увидеть много нежелательных белых точек. Чтобы устранить эти точки, мы можем провести операцию по эрозии. Но после эрозии мы можем получить тонкие и разъединенные белые линии судоку. Чтобы сделать их ясными и связанными, мы должны использовать операцию расширения. Таким образом, мы должны использовать комбинацию операции эрозии и расширения, когда мы должны сначала выполнить операцию эрозии, а затем операцию расширения. В основном эта операция называется открытием, что означает сначала эрозию, а затем операцию расширения. После операции открытия мы можем получить изображение ниже, как показано:

kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3, 3))
gray = cv2.morphologyEx(gray, cv2.MORPH_OPEN, kernel)
cv2.imwrite('morph.jpg',gray)

Шаг 5: получение судоку

Теперь из этого изображения мне нужна только часть судоку. Для этого мы будем использовать концепцию контуров в компьютерном зрении.

Контуры: кривая, полученная путем соединения всех непрерывных точек, имеющих одинаковые свойства (цвет или интенсивность). при работе с контурами следите за тем, чтобы объект был белого цвета, а фон - черным.

contours,h=cv2.findContours(gray,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)

Функция findContours принимает 3 аргумента:

  • серый: пороговое изображение, на котором объект окрашен в белый цвет, а фон - в черный цвет.
  • Метод получения контуров: здесь используется cv2.RETR_TREE, который извлекает все контуры и создает полный список семейной иерархии.
  • Метод аппроксимации контура: здесь используется cv2.CHAIN_APPROX_SIMPLE, что означает, что мы получим только 4 точки контура.

Из всех контуров мы возьмем один контур, который имеет наибольшую площадь, и мы знаем, что он будет соответствовать части судоку.

contour = max(contours, key=cv2.contourArea)

Найдя максимальную часть контура, мы должны обрезать эту часть. Для этого сначала нам нужно получить прямоугольные корды, которые включают этот контур. Это можно сделать с помощью cv2.boundingRect, которая дает нам прямой прямоугольник, не учитывая вращение объекта. Так что площадь ограничивающего прямоугольника не будет минимальной.

x, y, w, h = cv2.boundingRect(contour)
sudoku = gray[y:y + h, x:x + w]
side_length = min(sudoku.shape)
sudoku = cv2.resize(sudoku, (side_length, side_length))
cv2.imwrite('sudoku.jpg' , sudoku)

После определения x, y, w, h из функции boundingRect просто обрежьте эту часть изображения, а затем измените размер обрезанного изображения до минимальной длины стороны. После всех этих операций мы получим изображение как показано ниже:

Шаг 6. Как сделать судоку прямым

Теперь мы видим, что судоку не является прямым. Давайте сделаем это прямо.

contours, h = cv2.findContours(sudoku, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)

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

contours = sorted(contours, key=cv2.contourArea, reverse=True)

Здесь мы отсортировали контуры в порядке убывания их площади.

largest = None
for cnt in contours[:min(5,len(contours))]:
  print ("Length of approx(cnt) : " + str(len(approx(cnt))) )
  if (len(approx(cnt)) == 4)
    print ("Condition becomes True")
    largest = cnt

В приведенном выше коде мы использовали функцию ок., Указанную ниже.

def approx(cnt):
 peri = cv2.arcLength(cnt, True)
 app = cv2.approxPolyDP(cnt, 0.01 * peri, True)
 return app

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

Вы можете заметить здесь, что я использовал здесь cappolyDP, в основном эта функция принимает 3 аргумента. Первый - это cnt, которые являются точками контура, второй - 0,01 * peri, который представляет собой эпсилон, который является параметром точности. Чтобы получить правильный результат, необходим мудрый выбор эпсилона. Третий аргумент верен, чтобы показать, что кривая замкнута. В основном это означает, что наша функция даст аппроксимированную кривую, которая составляет примерно 1% длины дуги.

Теперь вернемся к предыдущему коду, в котором мы использовали эту функцию. У нас есть цикл по отсортированным контурам, передаем это в функцию примерно и проверяем, равно ли примерно (контуры) 4 или нет. Если условие удовлетворяется, тогда наибольшая переменная будет установлена ​​для этих переменных контура.

Итак, до сих пор мы получаем самые большие точки контура для изображений судоку.

Мы будем использовать 2 функции order_points и four_points_transform, которые объясняются ниже, после чего мы продолжим использовать эти функции.

def order_points(pts):
 pts = pts.reshape(4, 2)
 rect = np.zeros((4, 2), dtype = "float32")
 s = pts.sum(axis = 1)
 rect[0] = pts[np.argmin(s)]
 rect[2] = pts[np.argmax(s)]
 diff = np.diff(pts, axis = 1)
 rect[1] = pts[np.argmin(diff)]
 rect[3] = pts[np.argmax(diff)]
 return rect

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

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

Сначала мы инициализируем список координат, который будет упорядочен таким образом, чтобы

  • первая запись в списке находится вверху слева
  • вторая запись вверху справа
  • третий внизу справа
  • четвертый - внизу слева.

В заказанных кординатах:

  • в верхней левой точке будет наименьшая сумма
  • внизу справа будет самая большая сумма
  • После вычисления разницы между точками = ›В правом верхнем углу будет наименьшая разница.
  • После вычисления разницы между точками = ›Внизу слева будет самая большая разница.

После вычисления всего этого мы вернули заказанные баллы.

Теперь поговорим о второй функции, которая называется four_points_transform:

def four_points_transform(image,rect):
  (tl,tr,br,bl) = rect
  widthBottom = np.sqrt(((br[0] - bl[0])**2) + ((br[1] - bl[1]) **    2))
  widthTop = np.sqrt(((tr[0] - tl[0]) ** 2) + ((tr[1] - tl[1])**2))
  maxWidth = max(widthBottom , widthTop)
  heightA = np.sqrt(((tr[0] - br[0]) ** 2) + ((tr[1] - br[1]) ** 2))
  heightB = np.sqrt(((tl[0] - bl[0]) ** 2) + ((tl[1] - bl[1]) ** 2))
  maxHeight = max(int(heightA), int(heightB))
  dst = np.array([
                 [0,0],
                 [maxWidth - 1,0],
                 [maxWidth-1 , maxHeight-1],
                 [0,maxHeight-1]] , dtype = 'float32')
  M = cv2.getPerspectiveTransform(rect,dst)
  warped=cv2.warpPerspective(image,M,(int(maxWidth),int(maxHeight)))
  return warped

Как видите, эта функция принимает 2 аргумента - ›изображение (которое является предыдущим перекошенным изображением судоку) и rect (которое представляет собой упорядоченные точки).

  • Из прямоугольника мы получаем 4 точки, которые соответствуют (верхний левый, верхний правый, нижний правый, нижний левый)
  • Теперь нам нужно найти Bottom-Width и Top-Width, используя указанные выше точки, и мы можем найти это, используя евклидово расстояние. Логика нахождения то, что показано в коде. После этого мы выясняем максимум как Bottom-Width, так и Top-Width. Такая же процедура выполняется и на высоте.
  • После определения maxWidth и maxHeight мы составили массив из 4 точек, соответствующих точкам назначения. Эти 4 точки обозначаются как: [0,0], [maxWidth-1, 0], [maxWidth-1, maxHeight-1], [0, maxHeight-1].
  • Затем мы находим матрицу преобразования, которая меняет упорядоченные точки на точки назначения (cv2.getPerspectiveTransform)
  • После определения матрицы преобразования мы применяем это преобразование к точкам изображения (cv2.warpPerspective)
  • После применения трансформации мы вернули эти трансформированные точки.

Теперь мы на завершающих этапах обработки изображения.

if (largest is not None):
 app = approx(largest) 
 print ("App: " + str(len(app)))
 corners = order_points(app)
 sudoku = four_points_transform(sudoku,corners)
 print ("Done Straighten !!")
 cv2.imwrite("FinalSudoku.jpg" ,sudoku)

В приведенном выше коде мы получаем приблизительные 4 точки, используя функцию приближения, которая объяснена выше. После этого мы передаем эти точки в функцию order_points, чтобы получить упорядоченные точки, которые я назвал угловыми. Мы передаем эти точки в four_points_transform, чтобы получить настоящую прямую судоку, показанную ниже:

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

Спасибо.