Pull to refresh

Поиск людей на фотографиях на Android с помощью OpenCV

Reading time 6 min
Views 42K
Недавеча столкнулся с одной интересной задачкой для мобильного “коня” на Android’e- необходимо определить контуры людей на фотографиях (если таковы там были, естественно). После поиска в интернете, было решено использовать open source проект OpenCV, который может работать на платформе Android.

О нем уже было много написано, но данный сабж мной найден не был и был собран из нескольких источников и личных наблюдений.



Настройка


foldersНебольшое описание как включить библиотеку в проект на Android Studio (с применением gradle):
Для начала работы необходимо скачать последнюю версию библиотеки с сайта и скопировать содержимое папки OpenCV-2.4.8-android-sdk/sdk/java из архива в папку libs/OpenCV вашего проекта (при необходимости — создать).
Далее подключаем данный модуль в файлы gradle:
В корневой папке проекта редактируем settings.gradle и добавляем наш модуль:
include ':app',':app:libs:OpenCV'

В файле build gradle нашего приложения (не в корневой файл, а app/build.gradle) добавляем строчку compile project(':app:libs:OpenCV') в секцию dependencies, дабы получилось:
dependencies {
    compile 'com.android.support:appcompat-v7:+'
    compile project(':app:libs:OpenCV')
}

И создаем файл build.gradle в папке OpenCV с кодом:
buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:0.6.+'
    }
}

apply plugin: 'android-library'

repositories {
    mavenCentral();
}

android {
    compileSdkVersion 19
    buildToolsVersion "19"

    defaultConfig {
        minSdkVersion 8
        targetSdkVersion 19
    }

    sourceSets {
        main {
            manifest.srcFile 'AndroidManifest.xml'
            java.srcDirs = ['src']
            resources.srcDirs = ['src']
            aidl.srcDirs = ['src']
            renderscript.srcDirs = ['src']
            res.srcDirs = ['res']
            assets.srcDirs = ['assets']
        }
    }
}

Ну, собственно говоря, все, OpenCV Android SDK подключена и мы можем переходить к реализации.

Установка библиотек на устройство


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

Существует и другой путь подключения- статическая инициализация, но разработчики утверждают, что она существует только для девелоперских целей и, как мне кажется, может быть убрана в новых версиях (It is designed mostly for development purposes. This approach is deprecated for the production code, release package is recommended to communicate with OpenCV Manager via the async initialization described above.).

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

Для установки OpenCV Manager на эмулятор воспользуемся утилитой adb из нашего Android SDK. Для этого запустите виртуальное устройство, дождитесь его загрузки и выполните команду:
/PATH_TO_ANDROID_SDK/platform-tools/adb install /PATH_TO_OPENCV/OpenCV-2.4.8-android-sdk/apk/OpenCV_2.4.8_Manager_2.16_armv7a-neon.apk (выбрав подходящий под ABI apk).

Работа с изображениями


Вся инициализация OpenCV в приложении заключается в реализации callback интерфейса BaseLoaderCallback, где есть один метод onManagerConnected, в котором можем и уже можем начинать работу с OpenCV, и вызове статического метода OpenCVLoader.initAsync, с передачей в него необходимых параметров(включая callback). Если Ваше приложение не найдет OpenCV Manager, то попросит пользователя его установить. Подключение:
    @Override
    public void onResume()
    {
        super.onResume();
        //Вызываем асинхронный загрузчик библиотеки
        OpenCVLoader.initAsync(OpenCVLoader.OPENCV_VERSION_2_4_8, this, mLoaderCallback);
    }

    private BaseLoaderCallback mLoaderCallback = new BaseLoaderCallback(this) {
        @Override
        public void onManagerConnected(int status) {
            switch (status) {
                case LoaderCallbackInterface.SUCCESS:
                {
		//Мы готовы использовать OpenCV
                } break;
                default:
                {
                    super.onManagerConnected(status);
                } break;
            }
        }
    };

Теперь смело можем работать с нашей библиотекой.

В данном примере мы создаем Bitmap из url фотографии, далее переводим ее в объект OpenCV Mat (матрица изображения), конвертируем из цветной в градацию серого (это требуется для анализатора) и вызовем статический метод объекта HOGDescriptor.detectMultiScale (в который предварительно добавляем стандартный детектор человеческих контуров из метода HOGDescriptor.getDefaultPeopleDetector). После вызова, в переменной locations будут содержаться объекты прямоугольных областей нахождения людей (x, y, width, height), а в weights — релевантность поиска (но, как показала практика, она не совсем отвечает действительности при таких изображениях).

Для простоты я залил фотографии на facebook и объединил методы закачки и обработки фотографий в один. Сам код метода:
public Bitmap peopleDetect ( String path ) {
        Bitmap bitmap = null;
        float execTime;
        try {
            // Закачиваем фотографию
            URL url = new URL( path );
            HttpURLConnection connection = (HttpURLConnection) url.openConnection();
            connection.setDoInput(true);
            connection.connect();
            InputStream input = connection.getInputStream();
            BitmapFactory.Options opts = new BitmapFactory.Options();
            opts.inPreferredConfig = Bitmap.Config.ARGB_8888;
            bitmap = BitmapFactory.decodeStream(input, null, opts);
            long time = System.currentTimeMillis();
            // Создаем матрицу изображения для OpenCV и помещаем в нее нашу фотографию
            Mat mat = new Mat();
            Utils.bitmapToMat(bitmap, mat);
            // Переконвертируем матрицу с RGB на градацию серого
            Imgproc.cvtColor(mat, mat, Imgproc.COLOR_RGB2GRAY, 4);
            HOGDescriptor hog = new HOGDescriptor();
            //Получаем стандартный определитель людей и устанавливаем его нашему дескриптору
            MatOfFloat descriptors = HOGDescriptor.getDefaultPeopleDetector();
            hog.setSVMDetector(descriptors);
            // Определяем переменные, в которые будут помещены результаты поиска ( locations - прямоугольные области, weights - вес (можно сказать релевантность) соответствующей локации)
            MatOfRect locations = new MatOfRect();
            MatOfDouble weights = new MatOfDouble();
            // Собственно говоря, сам анализ фотографий. Результаты запишутся в locations и weights
            hog.detectMultiScale(mat, locations, weights);
            execTime = ( (float)( System.currentTimeMillis() - time ) ) / 1000f;
            //Переменные для выделения областей на фотографии
            Point rectPoint1 = new Point();
            Point rectPoint2 = new Point();
            Scalar fontColor = new Scalar(0, 0, 0);
            Point fontPoint = new Point();
            // Если есть результат - добавляем на фотографию области и вес каждой из них
            if (locations.rows() > 0) {
                List<Rect> rectangles = locations.toList();
                int i = 0;
                List<Double> weightList = weights.toList();
                for (Rect rect : rectangles) {
                    float weigh = weightList.get(i++).floatValue();

                    rectPoint1.x = rect.x;
                    rectPoint1.y = rect.y;
                    fontPoint.x  = rect.x;
                    fontPoint.y  = rect.y - 4;
                    rectPoint2.x = rect.x + rect.width;
                    rectPoint2.y = rect.y + rect.height;
                    final Scalar rectColor = new Scalar( 0  , 0 , 0  );
                    // Добавляем на изображения найденную информацию
                    Core.rectangle(mat, rectPoint1, rectPoint2, rectColor, 2);
                    Core.putText(mat,
                            String.format("%1.2f", weigh),
                            fontPoint, Core.FONT_HERSHEY_PLAIN, 1.5, fontColor,
                            2, Core.LINE_AA, false);

                }
            }
            fontPoint.x = 15;
            fontPoint.y = bitmap.getHeight() - 20;
            // Добавляем дополнительную отладочную информацию
            Core.putText(mat,
                    "Processing time:" + execTime + " width:" + bitmap.getWidth() + " height:" + bitmap.getHeight() ,
                    fontPoint, Core.FONT_HERSHEY_PLAIN, 1.5, fontColor,
                    2, Core.LINE_AA, false);
            Utils.matToBitmap( mat , bitmap );
        } catch (IOException e) {
            e.printStackTrace();
        }
        return bitmap;
    }

На выходе получаем bitmap с наложенными на него областями предположительного нахождения людей, весом данного результата поиска и некой дополнительной информацией. Скорость обработки одной фотографии(до тысячи пикселей в ширину и высоту) на Samsung Galaxy S3 составляет около 1-6 секунд. Ниже — результаты поиска со временем выполнения.

На первой фотографии анализатор не нашел ни одного человека, как бы нам не хотелось(
Image-1.jpg width:488 height:420 executionTime:1.085
image


Далее результат получше, но тоже не то
Image-2.jpg width:575 height:400 executionTime:1.226
image


Да и третий подкачал
Image-3.jpg width:618 height:920 executionTime:6.459
image


Уже что-то
Image-4.jpg width:590 height:505 executionTime:3.084
image


Перейдя к более «живым» фото, результат получился для меня слегка неожиданным
Image-5.jpg width:604 height:453 executionTime:1.913
image


Памятник частично был распознан
Image-6.jpg width:960 height:643 executionTime:4.106
image


А вот и первая фотография, которая действительно показывает, для чего библиотека «заточена»
Image-7.jpg width:960 height:643 executionTime:2.638
image


На четком контрасте не получил нужный эффект
Image-8.jpg width:960 height:857 executionTime:3.293
image


Здесь ничего не определил
Image-9.jpg width:960 height:642 executionTime:2.264
image


Фотография с людьми на дальнем плане
Image-10.jpg width:960 height:643 executionTime:2.188
image


Крупный план, но безуспешно
Image-11.jpg width:960 height:639 executionTime:2.273
image


Вместо четырех- немного больше
Image-12.jpg width:960 height:640 executionTime:2.669
image


Как видно из полученных результатов, библиотека более предназначена для определения фото\видео с камер наблюдения, где можно выделить объект и подтвердить его следующими кадрами, а для фотографий, с заранее неизвестными планами, дает достаточно большую ошибку распознавания(можно фильтровать по весу, но тогда рискуем потерять много образов). Скорость анализа изображения пока не позволяет использовать OpenCV для большого количества фотографий, да и при работе в режиме реального времени, на данных мощностях и алгоритмах, может не успевать за потоком кадров.

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

Спасибо за прочтение!

Проект на GitHub.
Tags:
Hubs:
+22
Comments 11
Comments Comments 11

Articles