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

При дожде или снегопаде капли дождя или снежинки оставляют на видеокадрах треки — протяженные линии. Особенно ярко этот эффект проявляется в темное время суток при активации инфракрасной подсветки видеокамер.

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

Алгоритм выявления атмосферных осадков

Для начала проведем подготовительную работу: импортируем библиотеки opencv и numpy, инициализируем захват кадров из видеофайла с именем, записанным в переменной fname, и создадим экземпляр класса backSub для исключения фона.

import cv2 as cv
import numpy as np
cap = cv2.VideoCapture(fname)
backSub = cv2.createBackgroundSubtractorMOG2(25, 16, False)

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

while True:
    ret, im_src = cap.read()
    if not ret:
        break

Считанный кадр im_src представляет собой RGB-изображение разрешением 1920x1080 пикселей.

Далее выполним несколько операций обработки исходного видеокадра.

Первая операция — вычитание фона.

fg_mask = backSub.apply(frame)

Результатом этой операции является grayscale-изображение fg_mask,  содержащее маску движущихся объектов.

Теперь выполним фильтрацию на основе морфологической операции открытия с ядром 3x3.

kernel = np.ones((3,3),np.uint8)
filtered = cv.morphologyEx(fg_mask, cv.MORPH_OPEN, kernel)

В результате с изображения исчезнут отдельные мелкие артефакты.

Далее с помощью функции findContours выделим контуры.

cnt_all, hierarchy = cv.findContours(filtered,\
                                     cv.RETR_EXTERNAL,\
                                     cv.CHAIN_APPROX_SIMPLE)

Для иллюстрации выполним обводку найденных контуров.

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

min_area = 16
cnt_sel_by_area = []
for cnt in cnt_all:
    if cv.contourArea(cnt, False) >= min_area:
        cnt_sel_by_area.append(cnt)

Результат:

Для того, чтобы выбрать контуры, соответствующие протяженным объектам, рассчитаем коэффициент формы — отношение квадрата периметра контура к площади контура с коэффициентом пропорциональности 1 / (4⨯pi). Для круглых контуров этот коэффициент минимален и равен единице. Чем более вытянут контур, тем больше значение этого коэффициента. В качестве порогового значения установим 4, затем выберем контуры с коэффициентом формы, превышающим выбранный порог.

shape_coef_min = 4
cnt_sel_by_shape = []
for cnt in cnt_sel_by_area:
    peri = cv.arcLength(cnt, True)
    area = cv.contourArea(cnt)
    shape_coef = (peri ** 2) / area / (4 * np.pi)
    if shape_coef >= shape_coef_min:
        cnt_sel_by_shape.append(cnt)

Визуально оценим результат этого действия:

Переходим к отбору контуров по направлению. Для того, чтобы определить ориентацию контуров относительно вертикали, используем аппроксимацию контуров прямыми линиями с помощью функции fitLine. Среди возвращаемых этой функцией значений есть координаты направляющего вектора прямой vx и vy. Оценить угол между прямой и вертикалью можно через скалярное произведение направляющего вектора прямой и направляющего вектора оси ординат. Значение этого угла может лежать в диапазоне от 0 до 180 градусов. Для удобства приведем это значение в диапазон 0..90 угловых градусов. Установим в качестве порога максимального отклонения значение 30 градусов и выберем контуры, удовлетворяющие этому условию.

max_angle = 30
cnt_sel = []
for cnt in cnt_sel_by_shape:
    vx, vy, x, y = cv.fitLine(cnt, cv.DIST_L2, 0, 0.01, 0.01).flatten()
    vect_line = np.array([vx, vy])
    vect_y_axis = np.array([0, 1])
    dot_product = np.dot(vect_line, vect_y_axis)
    angle = np.rad2deg(np.arccos(dot_product))
    angle = min(angle, 180 - angle)
    if angle <= max_angle:
        cnt_sel.append(cnt)

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

Выполним последнее действие: рассчитаем количество выбранных контуров

n_cnt_curr = len(cnt_sel)

Для иллюстрации нанесём выбранные контуры на исходный видеокадр.

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

decision_thr = 3

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

alpha = 0.1
n_cnt_avg = n_cnt_curr * alpha + n_cnt_avg * (1 - alpha)
n_cnt_avg_int = int(round(n_cnt_avg))

Далее сравним усредненное значение с пороговым  и сделаем вывод о наличии осадков.

is_precipitation = n_cnt_avg_int >= decision_thr
concl_dict_ru = {True: 'есть осадки', False: 'нет осадков'}
conclusion = concl_dict_ru[is_precipitation]

Проиллюстрируем промежуточные расчеты, динамику количества контуров и гипотезу о наличии осадков.

Видео с демонстрацией работы описанного алгоритма можно здесь или здесь. Кадр из демонстрации приведен ниже.

 

Код размещен в репозитории автора.

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

Комментарии (5)


  1. avdx
    01.06.2022 12:14
    +1

    А почему у вас начиная с определенного шага размер картинки изменяется, т.е. по факту анализируется только часть картинки? Ну и из-за разных размеров, при наложение контуров на кадр, контуры не соответствуют объектам в кадре.


    1. NewTechAudit Автор
      03.06.2022 07:04

      В приведенном примере производится обработка видео разрешением 1920x1080. Все три промежуточных изображения (im_src, fg_mask, filtered) имеют то же разрешение. Подскажите, пожалуйста, в какой части кода встречается resize?


      1. avdx
        03.06.2022 12:15

        Ну, если нумеровать картинки по порядку, то неужели вы сами не видите, что картинка №5 является увеличенной областью(верхняя левая часть) картинки №4? Например большая горизонтальная структура на этих картинках имеет разный размер. Такое могло произойти либо при использовании части картинки №4 (что я и предположил), либо при увеличении масштаба на картинке (с увеличением размера в пикселях) и последующем обрезании до старого размера (возможный вариант, раз вы утверждаете, что размер в пикселях картинок одинаковый).

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


  1. fio
    01.06.2022 18:00
    +1

    если в кадре есть некоторое количество линий, ориентированных преимущественно вертикально, то можно предположить, что есть атмосферные осадки: дождь или снег.

    Как поведет себя алгоритм если идет косой дождь (с ветром)?


    1. NewTechAudit Автор
      03.06.2022 07:04

      Алгоритм рассчитан на такие условия. Критерий вертикальности задается параметром max_angle. Если угол отклонения контура от вертикали меньше, чем значение этого параметра (к примеру, 30 градусов), то принимается, что контур связан с осадками. Таким образом, чем больше значение этого параметра, тем более косые дожди алгоритм будет выявлять.