Прежде чем говорить о сути вещей, договоримся о терминах . . .

Контур — просто непрерывная кривая, разделяющая на изображении все точки с одинаковым цветом или интенсивность. Контур является полезным инструментом для анализа форм, а также для обнаружения и распознавания объектов.

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

Начинаем наши упражнения

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

 Круг Омбре - изображение, сделанное с помощью фотошопа
Круг Омбре — изображение, сделанное с помощью фотошопа

Для своих практических упражнений заберите это изображение здесь.

Разделим изображение на 17 колец по уровням серого, а затем измерим площадь каждого кольца, ограниченного контуром.

import cv2
import numpy as np

def viewImage(image):
    cv2.namedWindow('Display', cv2.WINDOW_NORMAL)
    cv2.imshow('Display', image)
    cv2.waitKey(0)
    cv2.destroyAllWindows()

def grayscale_17_levels (image):
    high = 255
    while(true):  
        low = high - 15
        col_to_be_changed_low = np.array([low])
        col_to_be_changed_high = np.array([high])
        curr_mask = cv2.inRange(gray, col_to_be_changed_low,col_to_be_changed_high)
        gray[curr_mask > 0] = (high)
        high -= 15
        if(low == 0 ):
            break

image = cv2.imread('./path/to/image')
viewImage(image)
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
grayscale_17_levels(gray)
viewImage(gray)
То же изображение разделено на 17 уровней серого
То же изображение разделено на 17 уровней серого
def get_area_of_each_gray_level(im):

## convert image to gray scale (must br done before contouring)
    image = cv2.cvtColor(im, cv2.COLOR_BGR2GRAY)
    output = []
    high = 255
    first = True
    while(true):

        low = high - 15
        if(first == False):

            ## making values that are of a greater gray level black 
            ## so it won't get detected  
            to_be_black_again_low = np.array([high])
            to_be_black_again_high = np.array([255])
            curr_mask = cv2.inRange(image, to_be_black_again_low, 
            to_be_black_again_high)
            image[curr_mask > 0] = (0)
            
        # making values of this gray level white so we can calculate
        # it's area
        ret, threshold = cv2.threshold(image, low, 255, 0)
        contours, hirerchy = cv2.findContours(threshold, 
        cv2.RETR_LIST, cv2.CHAIN_APPROX_NONE)

        if(len(contours) > 0):
            output.append([cv2.contourArea(contours[0])])
            cv2.drawContours(im, contours, -1, (0,0,255), 3)

        high -= 15
        first = False
        if(low == 0 ):
break
return output

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

Второй шаг — получение порогового (бинарного) изображения. Теперь поле, которое я хочу обвести контуром надо сделать белым, а все остальные — чёрными. Этот шаг не сильно оригинален, но он необходим, потому как оконтуривание лучше всего делать по черно-белым (пороговым, бинарным) изображениям.

Получим (пороговое, бинарное) изображение, где белое кольцо заменит серое с интенсивностью 10-го уровня (255–15 * 10), а все остальные кольца сделаем чёрными.

Выделяем единственный, 10-й сегмент, для вычисления его площади
Выделяем единственный, 10-й сегмент, для вычисления его площади

image = cv2.imread('./path/to/image')
print(get_area_of_each_gray_level(image))
viewImage(image)

 Контуры 17 уровней серого на исходном изображении
Контуры 17 уровней серого на исходном изображении


 

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

Зачем всё это?

Прежде чем продолжить, хочу подчеркнуть важность нашей темы.

На факультете компьютерной инженерии MTI, с которым у нас активное сотрудничество, ведутся работы над проектом под названием «Машинное обучение» для интеллектуального обнаружения и идентификации опухолей.

В этом проекте в полный рост используется сегментация цветного изображения для того, чтобы научить компьютер обнаруживать опухоль. При работе с МРТ (Магнитно-резонансная томография) программа должна определять стадию развития рака, что достигается сегментированием сканированных изображений по различным уровням оттенков серого, где самые темные участки наиболее заполнены раковыми клетками, а близкие к белому соответствуют более здоровым частям тела. Далее вычисляется степень развития опухоли, которой соответствует некоторый уровень серого. Именно эта информация позволяет локализовать и определить стадию развития опухоли.

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

Обнаружение объекта

Фото Лукаса из Pexels
Фото Лукаса из Pexels

Для своих упражнений заберём изображение отсюда, на Pexels, которое нужно будет просто немного обрезать.

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

 ПРИМЕЧАНИЕ  Вот результат оконтуриавания без какой-либо предварительной обработки. Здесь я просто хочу показать, как неровномерная природа листа обманывает OpenCV и не позволяет ему понять, что это всего лишь один объект. Кстати, очень полезный мануал по OpenCV

Контур без предварительной обработки, обнаружен 531 контур
Контур без предварительной обработки, обнаружен 531 контур

import cv2
import numpy as np

def viewImage(image):
    cv2.namedWindow('Display', cv2.WINDOW_NORMAL)
    cv2.imshow('Display', image)
    cv2.waitKey(0)
    cv2.destroyAllWindows()

Первое, что вам необходимо знать — HSV (цветовая модель) ваших цветов, которую можно узнать, сделав преобразование его из RGB в HSV, использовав нижеследующий код.

## getting green HSV color representation
green = np.uint8([0, 255, 0])
green_hsv = cv2.cvtColor(green,cv2.COLOR_BGR2HSV)
print( green_hsv)
Зеленый цвет в HSV-представлении
Зеленый цвет в HSV-представлении

Преобразование изображения в HSV-представление: с HSV значительно проще получить полный диапазон одного цвета. В HSV H — тон (например, красный, зелёный или сине-голубой), S — насыщенность (варьируется в пределах 0—100 или 0—1. Чем больше этот параметр, тем «чище» цвет, поэтому этот параметр иногда называют чистотой цвета. А чем ближе этот параметр к нулю, тем ближе цвет к нейтральному серому), V — значение или Brightness — яркость (задаётся в пределах 0—100 или 0—1). Мы уже знаем, что зеленому цвету в HSV соответствует [60, 255, 255]. Все зеленые цвета мира расположены в пределах от [45, 100, 50] до [75, 255, 255], то есть от [60–15, 100, 50] до [60+15 , 255, 255]. 15 — только приблизительное значение.

Мы берем этот диапазон и преобразуем его в [75, 255, 200 ] или любой другой цвет из диапазона зелёных (3-е значение, яркость, должно быть относительно большим), это значение, когда мы перейдём к бинарному изображению, станет белым.

image = cv2.imread('./path/to/image.jpg')
hsv_img = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
viewImage(hsv_img) ## 1

green_low = np.array([45 , 100, 50] )
green_high = np.array([75, 255, 255])
curr_mask = cv2.inRange(hsv_img, green_low, green_high)
hsv_img[curr_mask > 0] = ([75,255,200])
viewImage(hsv_img) ## 2

## converting the HSV image to Gray inorder to be able to apply 
## contouring
RGB_again = cv2.cvtColor(hsv_img, cv2.COLOR_HSV2RGB)
gray = cv2.cvtColor(RGB_again, cv2.COLOR_RGB2GRAY)
viewImage(gray) ## 3

ret, threshold = cv2.threshold(gray, 90, 255, 0)
viewImage(threshold) ## 4

contours, hierarchy =  cv2.findContours(threshold,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
cv2.drawContours(image, contours, -1, (0, 0, 255), 3)
viewImage(image) ## 5

Изображение сразу после преобразования в HSV (1)
Изображение сразу после преобразования в HSV (1)

Изображение после нанесения маски (нормализация цвета) (2)
Изображение после нанесения маски (нормализация цвета) (2)

Изображение после преобразования из HSV в серый (3)
Изображение после преобразования из HSV в серый (3)

Бинарное изображение, последний шаг (4)
Бинарное изображение, последний шаг (4)

Конечный контур (5)
Конечный контур (5)

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

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

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

def findGreatesContour(contours):
    largest_area = 0
    largest_contour_index = -1
    i = 0
    total_contours = len(contours)
    while (i  largest_area):
            largest_area = area
            largest_contour_index = i
        i+=1
            
    return largest_area, largest_contour_index

# to get the center of the contour
cnt = contours[13]
M = cv2.moments(cnt)
cX = int(M["m10"] / M["m00"])
cY = int(M["m01"] / M["m00"])

largest_area, largest_contour_index = findGreatesContour(contours)

print(largest_area)
print(largest_contour_index)

print(len(contours))

print(cX)
print(cY)

Результат печати
Результат печати

 

Некоторые примеры кода из этой статьи можно загрузить здесь.
 

По мотивам Object detection via color-based image segmentation using python

Опубликовано Вадим В. Костерин

ст. преп. кафедры ЦЭиИТ. Автор более 130 научных и учебно-методических работ. Лауреат ВДНХ (серебряная медаль).

Оставьте комментарий

Ваш адрес email не будет опубликован.