Пошук людей на фотографіях на Android за допомогою OpenCV

Недавеча зіткнувся з однією цікавою задачкой для мобільного "коня" на 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 .

Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.