Время прочтения: 4 мин.

Получим кадры пустых улиц на примере Токио – одного из самых густонаселённых городов планеты. За основу возьмём раскадровку видео с камеры наблюдения в районе Shinjuku, плотность населения которого составляет более 19 тыс. человек на кв. км.

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

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

Под капотом MedianFilter устроен довольно просто: находит средние значения RGB из заданного массива пикселей:

func medianPixel(pixels []Pixel) Pixel {

…

  rMedian := rValues[int(len(rValues)/2)]
  gMedian := gValues[int(len(gValues)/2)]
  bMedian := bValues[int(len(bValues)/2)]

…
}

Далее medianFilter() создает изображение с каждым пикселем, сгенерированным с помощью предыдущей функции medianPixel(). Она начинает с чтения всех входных кадров и создания объектов изображения для каждого из них. Затем она перебирает все пиксели, сгенерированными с помощью функции medianPixel(), и создает новое изображение с усреднёнными пикселями. Все кадры обрабатываются параллельно благодаря sync.WaitGroup:

func medianFilter(filePaths []string) (*Image, error) {
  var images []*Image
  for _, filePath := range filePaths {
    img, err := newImage(filePath)
    if err != nil {
      return nil, err
    }
    images = append(images, img)
  }

  if len(images) < 5 {
    return nil, errors.New("not enough images to perform noise reduction")
  }

  outputImage := images[0]

  heigth := outputImage.Height
  width := outputImage.Width
  for _, img := range images {
    if heigth != img.Height || width != img.Width {
      return nil, errors.New("at least one image has a different width or height")
    }
  }

  var wg sync.WaitGroup

  for rowIndex := 0; rowIndex < heigth; rowIndex++ {
    wg.Add(1)

    go (func(rowIndex int) {
      for colIndex := 0; colIndex < width; colIndex++ {
        var pixels []Pixel
        for _, img := range images {
          pixels = append(pixels, img.Pixels[rowIndex][colIndex])
        }

        medPixel := medianPixel(pixels)
        outputImage.Pixels[rowIndex][colIndex].set("R", medPixel.R)
        outputImage.Pixels[rowIndex][colIndex].set("G", medPixel.G)
        outputImage.Pixels[rowIndex][colIndex].set("B", medPixel.B)
      }
      wg.Done()
    })(rowIndex)
  }

  wg.Wait()
  return outputImage, nil
}

Это лишь часть кода, но и в нём необязательно разбираться, так как для запуска обработки достаточно запустить следующий код:

package main

import (
	"fmt"
	"github.com/koraygocmen/MedianFilter"
	"io/ioutil"
)

func main() {
	folder := "custom_folder"
	files, err := ioutil.ReadDir("./"+folder+"/")
	if err != nil {
		fmt.Println(err)
	}

	var imagePath []string
	for _, f := range files {
		imagePath = append(imagePath, "./"+folder+"/"+f.Name())
	}

	if err := MedianFilter.RemoveMovingObjs(imagePath, "./"+folder+".jpg"); err != nil {
		fmt.Println(err)
	}
}

В качестве примера мы взяли около 100 случайных кадров из нескольких минут видео с live-камеры, о которой упоминается выше. Примеры исходных кадров:

В результате обработки получим вот такой кадр:

Аналогичный вариант в ночное время. Было:

Стало:

Как видно, на итоговых кадрах имеются небольшие артефакты в виде «машин-призраков», а также единичных пешеходов. Такие объекты основную часть видео были неподвижны. Это можно исправить путём взятия кадров с большего промежутка времени или настройки самой библиотеки.

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

Дальнейший шаг – получение «очищенного» видео.