Вот потенциальное решение с использованием морфологических операций с OpenCV
Получение бинарного изображения. Загрузка изображения, оттенки серого, размытие по Гауссу , Порог Оцу
Получение масок горизонтальных / вертикальных линий. Создание горизонтального / вертикального ядра и изоляция горизонтальных / вертикальных линий сетки с помощью cv2.getStructuringElement
и cv2.morphologyEx
Объединение масок. Побитовые и маски вместе для завершения сетки
Заполните отдельные отверстия сетки. Найдите контуры и зафиксируйте отверстия, заполнив каждую ячейку сетки
Двоичное изображение
Горизонтальная маска (слева) и вертикальная маска (справа)
Комбинированные маски
Ремонт отдельных отверстий сетки
Инвертировать для результата
import cv2
# Load image, grayscale, blur, Otsu's threshold
image = cv2.imread('1.jpg')
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
blur = cv2.GaussianBlur(gray, (3,3), 0)
thresh = cv2.threshold(blur, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)[1]
# Obtain horizontal lines mask
horizontal_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (50,1))
horizontal_mask = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, horizontal_kernel, iterations=1)
horizontal_mask = cv2.dilate(horizontal_mask, horizontal_kernel, iterations=9)
# Obtain vertical lines mask
vertical_kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (1,50))
vertical_mask = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, vertical_kernel, iterations=1)
vertical_mask= cv2.dilate(vertical_mask, vertical_kernel, iterations=9)
# Bitwise-and masks together
result = 255 - cv2.bitwise_or(vertical_mask, horizontal_mask)
# Fill individual grid holes
cnts = cv2.findContours(result, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
for c in cnts:
x,y,w,h = cv2.boundingRect(c)
cv2.rectangle(result, (x, y), (x + w, y + h), 255, -1)
cv2.imshow('thresh', thresh)
cv2.imshow('vertical_mask', vertical_mask)
cv2.imshow('horizontal_mask', horizontal_mask)
cv2.imshow('result', result)
cv2.waitKey()