FindContours одного канала в OpenCv (Python)

Bastian спросил: 28 марта 2018 в 01:58 в: python

У меня проблема с использованием метода findContours(...) OpenCV, чтобы найти контуры в одном канальном изображении. Изображение на самом деле представляет собой массив numpy с формой (128, 128) и элементами с реальными значениями между [0.0,1.0]. Первоначально форма (1,128,128,1), но я использовал np.squeeze(...), чтобы избавиться от первого и последнего измерения. Сохранение любого из них не решает мою проблему.

Что я пробовал:

image = np.squeeze(array) #using np.squeeze(array, [0]) won't help.
image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
contours, hierarchy = cv2.findContours(image, 1, 2)

Приведенный выше код вызывает следующее исключение:

error: (-215) scn == 3 || scn == 4 in function cv::cvtColor

То, что я также пробовал:

Если я применяю findContours(...) напрямую, поэтому без использования cvtColor(...), я получаю другую ошибку:

 error: (-210) [Start]FindContours supports only CV_8UC1 images when mode != CV_RETR_FLOODFILL otherwise supports CV_32SC1 images only in function cvStartFindContours_Impl

Некоторые источники предлагают использовать threshold для получения двоичного изображения, которое требуется findContours(...) [1]

ret, thresh = cv2.threshold(image, 1, 255, 0)
im2, contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)

Это не поможет, и я получаю то же самое исключение, которое жалуется на поддержку CV_8UC1 .


3 ответа

Есть решение
Dan Mašek ответил: 28 марта 2018 в 05:55

Изображение на самом деле представляет собой пустой массив с формой (128, 128) и элементами с реальными значениями между [0.0,1.0].

Ошибка из cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) было связано с тем, что вы пытаетесь преобразовать одноканальное изображение из BGR (3 канала) в оттенки серого (1 канал). Ваше изображение уже в градациях серого, поэтому этот шаг не требуется.

Ошибка cv2.findContours произошла из-за неправильного типа данных элементов в массиве. В документации сказано следующее о входном изображении:

Source, 8-битное одноканальное изображение. Ненулевые пиксели рассматриваются как 1. Нулевые пиксели остаются 0, поэтому изображение рассматривается как двоичное. Вы можете использовать compare, inRange, threshold, adaptiveThreshold, Canny и другие для создания двоичного изображения из оттенков серого или цвета. Если режим равен RETR_CCOMP или RETR_FLOODFILL, вход также может быть 32-разрядным целочисленным изображением меток (CV_32SC1).

Чтобы исправить это, вам нужно масштабировать значения в вашем изображении в диапазоне [0.0,255.0], а затем преобразовать результат в np.uint8:

image_8bit = np.uint8(image * 255)

Есть несколько других проблем или причуд в отношении кода в вашем вопросе.

Прежде всего, в одном фрагменте cv2.findContours возвращает 2 значения (OpenCV 2. x), а в другом он возвращает 3 значения (OpenCV 3.x). Какую версию вы используете?

Ваш первый пример кода содержит следующее:

contours, hierarchy = cv2.findContours(image, 1, 2)

Избегайте использования магических чисел. 1 соответствует cv2.RETR_LIST, а 2 соответствует cv2.CHAIN_APPROX_SIMPLE. Поскольку режим RETR_LIST не генерирует никакой иерархии, вы можете игнорировать это возвращаемое значение:

contours, _ = cv2.findContours(binarized, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)

Другая проблема, скорее всего, связана с тем, что изначально вы не делали ' • явным образом преобразовать изображение в двоичную форму (например, используя cv2.threshold). Хотя это не приведет к исключениям, результат, вероятно, не будет иметь большого смысла - findContours делит пиксели на две группы - нули, а затем все ненулевые. Скорее всего, вы захотите разделить их по-разному.

threshold_level = 127 # Set as you need...
_, binarized = cv2.threshold(image_8bit, threshold_level, 255, cv2.THRESH_BINARY)

Пример сценария (OpenCV 3.x):

import numpy as np
import cv2# Generate random image matching your description:
# shape is (128,128), values are real numbers in range [0,1]
image = np.random.uniform(0, np.nextafter(1,2), (128,128))# Scale and convert data type
image_8bit = np.uint8(image * 255)threshold_level = 127 # Set as you need...
_, binarized = cv2.threshold(image_8bit, threshold_level, 255, cv2.THRESH_BINARY)_, contours, hierarchy = cv2.findContours(binarized, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)# ... processing the contours, etc.
Bastian ответил: 28 марта 2018 в 06:02
Большое спасибо за ваш подробный ответ! Я скоро это проверю. Вы правы в своих предложениях, я не хотел вдаваться в подробности, потому что я только прототипировал что-то, используя python.
Ishara Madhawa ответил: 28 марта 2018 в 11:31

Попробуйте это:

cv2.threshold(image, 1, 255, cv2.THRESH_BINARY,image)
im2, contours, hierarchy = cv2.findContours(image, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
Bastian ответил: 28 марта 2018 в 02:37
Спасибо, но это все еще показывает то же самое поведение
Divakar ответил: 28 марта 2018 в 02:41
@BastianSchoettle Убедитесь, что dtype для image равен UINT8.
Dan Mašek ответил: 28 марта 2018 в 06:38
Это не очень полезно, так как не дает никаких объяснений и не решает проблему неправильного типа данных в image. [1] довольно бесполезен, так как вы используете необязательный аргумент"destination" и в любом случае отбрасываете возвращаемое значение.
A. Antony ответил: 28 марта 2018 в 02:05

Функция cv2.findContours принимает пороговые изображения. Ошибка возникает потому, что ваше входное изображение является изображением в градациях серого.

Bastian ответил: 28 марта 2018 в 02:06
Но я установил порог изображения во втором подходе?
A. Antony ответил: 28 марта 2018 в 02:10
Вы уверены, что правильно читаете изображение? Попробуйте вывести изображение, которое вы читаете, и посмотрите, что вы получите.
Bastian ответил: 28 марта 2018 в 02:15
Конечно, это действительно ценный 2d массив, который я могу без проблем построить, используя imshow (...).
Dan Mašek ответил: 28 марта 2018 в 05:09
Это просто неправильно, это сообщение об ошибке связано с неправильным типом данных / количеством каналов. Из документов: "Ненулевые пиксели рассматриваются как 1". Вы используете порог, так как в большинстве случаев вы хотите разделить значения по-разному.