Документация Engee

Обнаружение объектов с помощью HOG

В этом руководстве мы используем линейный метод SVM на основе дескриптора признаков «гистограмма направленных градиентов» (HOG) для создания детектора людей. Сначала мы создадим классификатор людей, а затем используем его со скользящим окном для идентификации и определения местонахождения людей на изображении.

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

Скачайте скрипт, чтобы получить обучающие данные. Скачайте архив tutorial.zip, распакуйте его и запустите файл getdata.bash. (Измените переменную path*to*tutorial в файле preprocess.jl и путь к исполняемому файлу Julia в файле getdata.bash.) Скрипт скачает необходимые наборы данных. Начнем с загрузки данных и вычисления признаков HOG для всех изображений.

using Images, ImageFeatures

path_to_tutorial = ""    # укажите этот путь
pos_examples = "$path_to_tutorial/tutorial/humans/"
neg_examples = "$path_to_tutorial/tutorial/not_humans/"

n_pos = length(readdir(pos_examples))   # количество положительных примеров обучения
n_neg = length(readdir(neg_examples))   # количество отрицательных примеров обучения
n = n_pos + n_neg                       # количество примеров обучения
data = Array{Float64}(undef, 3780, n)   # Массив для хранения дескриптора HOG каждого изображения. Каждое изображение в обучающих данных имеет размер 128x64, поэтому длина равна 3780
labels = Vector{Int}(undef, n)          # Вектор для хранения метки (1 = человек, 0 = не человек) каждого изображения.

for (i, file) in enumerate([readdir(pos_examples); readdir(neg_examples)])
    filename = "$(i <= n_pos ? pos_examples : neg_examples )/$file"
    img = load(filename)
    data[:, i] = create_descriptor(img, HOG())
    labels[i] = (i <= n_pos ? 1 : 0)
end

По сути, теперь в наших обучающих данных есть закодированная версия изображений. Такое кодирование фиксирует полезную информацию, а лишняя информация (изменения освещенности, вариации позы и т. д.) отбрасывается. На этих данных мы обучим линейную модель SVM.

using LIBSVM

#Разделяем набор данных на обучающий и тестовый. Обучающий набор = 2500 изображений, тестовый набор = 294 изображения.
random_perm = randperm(n)
train_ind = random_perm[1:2500]
test_ind = random_perm[2501:end]

model = svmtrain(data[:, train_ind], labels[train_ind]);

Теперь проверим этот классификатор на нескольких изображениях.

img = load("$pos_examples/per00003.ppm")
descriptor = Array{Float64}(3780, 1)
descriptor[:, 1] = create_descriptor(img, HOG())

predicted_label, _ = svmpredict(model, descriptor);
print(predicted_label)                          # 1 = человек, 0 = не человек

# Получаем тестовую точность модели
predicted_labels, decision_values = svmpredict(model, data[:, test_ind]);
@printf "Accuracy: %.2f%%\n" mean((predicted_labels .== labels[test_ind])) * 100 # тестовая точность должна быть > 98 %

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

Исходное изображение Исходное изображение

predicted_label = 1

predicted_label = 1

Исходное изображение Исходное изображение

predicted_label = 1

predicted_label = 0

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

Исходное изображение
img = load("path_to_tutorial/tutorial/humans.jpg")
rows, cols = size(img)

scores = Array{Float64}(22, 45)
descriptor = Array{Float64}(3780, 1)

#Применяем классификатор, используя метод скользящего окна, и сохраняем оценку классификации для класса «не человек» в каждой позиции в массиве оценок
for j = 32:10:cols-32
    for i = 64:10:rows-64
        box = img[i-63:i+64, j-31:j+32]
        descriptor[:, 1] = create_descriptor(box, HOG())
        predicted_label, s = svmpredict(model, descriptor)
        scores[Int((i - 64) / 10)+1, Int((j - 32) / 10)+1] = s[1]
    end
end
scores

Как видите, классификатор дал низкую оценку классу «не человек» (а значит, высокую оценку классу «человек») в позициях, соответствующих людям, на исходном изображении. Ниже мы устанавливаем пороговое значение для изображения и подавляем неминимальные значения, чтобы получить местоположения людей. Затем мы строим ограничивающие прямоугольники с помощью ImageDraw.

using ImageDraw, ImageView

scores[scores.>0] = 0
object_locations = findlocalminima(scores)

rectangles = [
    [
        ((i[2] - 1) * 10 + 1, (i[1] - 1) * 10 + 1),
        ((i[2] - 1) * 10 + 64, (i[1] - 1) * 10 + 1),
        ((i[2] - 1) * 10 + 64, (i[1] - 1) * 10 + 128),
        ((i[2] - 1) * 10 + 1, (i[1] - 1) * 10 + 128),
    ] for i in object_locations
];

for rec in rectangles
    draw!(img, Polygon(rec), RGB{N0f8}(0, 0, 1.0))
end
imshow(img)
boxes

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


Эта страница была создана с помощью DemoCards.jl и Literate.jl.