Невозможно найти положение таблицы на изображении с помощью opencv

Я хочу обрабатывать табличные данные из изображений. Для этого мы читаем изображение с помощью opencv и находим, где находится таблица, выполнив следующие семь шагов. На изображении номер 7 мы планируем обрезать по границе. В следующем примере данных все работает именно так, как я хочу. Это связано с тем, что внутренняя таблица изображения имеет черную внешнюю границу.

image = cv2.imread(image_path, cv2.IMREAD_COLOR)

grayscaled_image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

_, thresholded_image = cv2.threshold(grayscaled_image, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)

inverted_image = cv2.bitwise_not(thresholded_image)

dilated_image = cv2.dilate(inverted_image, None, iterations=3)

contours, _ = cv2.findContours(dilated_image, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
image_with_all_contours = image.copy()
cv2.drawContours(image_with_all_contours, contours, -1, (0, 255, 0), 2)

rectangular_contours = []
for contour in contours:
    peri = cv2.arcLength(contour, True)
    epsilon = peri * 0.02
    approx = cv2.approxPolyDP(contour, epsilon, True)
    if len(approx) == 4:
        rectangular_contours.append(approx)
image_with_only_rectangular_contours = image.copy()
cv2.drawContours(image_with_only_rectangular_contours, rectangular_contours, -1, (0, 255, 0), 2)

max_area = 0
max_area_contour = None
for contour in rectangular_contours:
    area = cv2.contourArea(contour)
    if area > max_area:
        max_area = area
        max_area_contour = contour

image_with_max_area_contour = image.copy()
cv2.drawContours(image_with_max_area_contour, [max_area_contour], -1, (0, 255, 0), 2)

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

Как видно на картинке выше, если внешней границы нет, в процессе получения Thresholded Image возникают проблемы. Позже выйти за квадратную линию контура, выполнив cv2.findContours, становится невозможно.

В конечном итоге я хочу прочитать значения в столбцах «Имя» и «Избранное» в Pandas. В настоящее время я слежу за процессом, ссылаясь на этот пост. Как выбрать прямоугольник наибольшей контурной линии?

ПОПРОБУЮ 1 от @Ivan

На следующем изображении показано, когда cv2.RETE_EXTERNAL применяется при выполнении cv2.findContours. Линия контура рисуется следующим образом. То есть самая внешняя часть не рисуется.

ПОПРОБУЕМ 2 от @cards

Если вы выберете 50*30 вместо размера ядра по умолчанию, будет выбран искаженный прямоугольник, как показано ниже.

dilated_image = cv2.dilate(inverted_image, np.ones((50, 30), np.uint8), iterations=1)

я думаю, ты забыл последнее изображение

Ivan 03.05.2024 16:51

В cv2.findContours есть опция RETR_EXTERNAL, позволяющая легко получить наиболее внешний контур.

Ivan 03.05.2024 16:54

вы можете выполнить расширение с «ультра» большим ядром (возможно, только вертикальным или горизонтальным или даже обоими), а затем использовать findContours с соответствующими параметрами, как уже предлагалось.

cards 03.05.2024 17:03

@Иван, я добавил недостающие фотографии, как вы упомянули, и применил RETR_EXTERNAL. К сожалению, это не работает так же хорошо, как отредактированное сообщение.

na_sacc 03.05.2024 17:10

@cards Я понимаю это базовое ядро ​​3*3. Если вы измените это значение на что-то большее, например 50*30, вы получите искаженный прямоугольник, который будет выбран неточно.

na_sacc 03.05.2024 17:14

как только у вас будет минимальный прямоугольник, возьмите его boundingRect(cnt)

cards 03.05.2024 17:23

получив контуры, сделайте цикл по всем из них, сравнивая contourArea, чтобы получить самый большой, затем примените boundingRect к нему

Ivan 03.05.2024 18:07

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

fmw42 03.05.2024 18:17

Посмотрите на красный канал вашего изображения. Этот желтый цвет имеет красный компонент 255. Все остальное <255. Как только вы избавитесь от темных линий по краям изображения, у вас получится идеальная маска.

beaker 03.05.2024 18:24
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
0
9
92
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Этот код создает изображение маски (похожее на ваше последнее обнаружение) и получает весь ограничивающий прямоугольник:

import cv2
import numpy as np

# Create a completely black mask image
mask = np.zeros((300, 400), dtype=np.uint8)

# Draw white rectangles
cv2.rectangle(mask, (140, 50), (150, 250), (255), thickness=cv2.FILLED)
cv2.rectangle(mask, (50, 100), (350, 150), (255), thickness=cv2.FILLED)

# Find contours
contours, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

# Get bounding rectangles of contours
bounding_rects = [cv2.boundingRect(cnt) for cnt in contours]

# Find the overall bounding rectangle
x, y, w, h = bounding_rects[0]
max_x = x + w
max_y = y + h
for rect in bounding_rects[1:]:
    x = min(x, rect[0])
    y = min(y, rect[1])
    max_x = max(max_x, rect[0] + rect[2])
    max_y = max(max_y, rect[1] + rect[3])
    w = max_x - x
    h = max_y - y

print(x, y, w, h)

# Draw the overall bounding rectangle
cv2.rectangle(mask, (x, y), (x + w, y + h), (255), 2)

# Save the mask image
cv2.imwrite('mask.png', mask)

# Draw the overall rectangle on a copy of the mask
mask_with_rectangle = mask.copy()
# Draw in gray
cv2.rectangle(mask_with_rectangle, (x, y), (x + w, y + h), (50), 2)

# Save the image with the overall rectangle
cv2.imwrite('mask_with_rectangle.png', mask_with_rectangle)

Вы можете заменить mask на свой dilated_image

Ответ принят как подходящий

Я не уверен, насколько общий ваш пример, в зависимости от этого мой ответ может измениться.

Начать:

%matplotlib notebook
import cv2
import numpy as np
import matplotlib.pyplot as plt
im = cv2.imread("table.jpg") # read im
b, g, r = cv2.split(im) # split to bgr
imRGB = cv2.merge([r,g,b]) # generate rgb image for plotting...

Подводя итог, всегда полезно взглянуть на различные цвета, составляющие изображение. Вот как ваше изображение выглядит в каналах RGB:

fig, axs = plt.subplots(nrows = 1, ncols = 3, sharex = True, sharey = True)
axs[0].imshow(r)
axs[1].imshow(g)
axs[2].imshow(b)
plt.suptitle("R|G|B")
plt.tight_layout()
for ax in axs:
    ax.axis("off")

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

Таким образом, пороговое значение имело бы смысл:

mask = (r<240).astype(np.uint8)
plt.figure()
plt.imshow(mask)
plt.axis("off")

Здесь мы видим, что порог действительно работает хорошо:

Мы можем выполнить небольшую операцию закрытия, а затем найти контуры и нарисовать контуры на изображении:

mask = (r<240).astype(np.uint8)
maskClosed = cv2.morphologyEx(mask, cv2.MORPH_CLOSE, np.ones((5,5)))
contours = cv2.findContours(maskClosed, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[0]
largestContour = max(contours, key=cv2.contourArea)
imRGB = cv2.drawContours(imRGB, [largestContour], 0, (255,0,0), 5)
plt.figure()
plt.imshow(imRGB)
plt.axis("off")

Мы получаем следующее:

Уже неплохо, мы можем использовать это с cv2.boundingRect(largestContour), чтобы получить координаты для обрезки: (104, 68, 389, 137)

Дайте мне знать, работает это для вас или нет..

Другие вопросы по теме