1. Готово
В этой главе я, Воля, шаг за шагом объясняю, как начать использовать. OpenCV Развивать зрительное восприятие Android приложение。
Компьютерное зрение с открытым исходным кодом(OpenCV)программное обеспечение Библиотекаиметь 2500 Несколько алгоритмов оптимизации; Библиотека включает полный набор классических и современных алгоритмов компьютерного зрения и машинного обучения. Стоит на складе уже десять лет,и на основеРаспространение программного обеспечения Беркли(BSD)许Может证выпускать,используйте 户易Использовать модификацию。
OpenCV Было скачано более 700 Тысячи раз и было Google,Yahoo,Microsoft,Intel,IBM,Sony и Honda И другие известные компании используют. Кроме того, OpenCV Поддержка различных настольных и мобильных операционных систем, включая Windows,Linux,Mac OSX,Android и iOS。
существоватьв этой книге,Мы Воляиспользовать Применимо к Android из OpenCV,этода Можетсуществовать Android запуститьиз на операционной системе OpenCV изодинчасть.
Я Воля представляю два варианта подготовки Установи. Во-первых, если вы начинаете новую установку Android, рекомендуется начать с Tegra Android развивать Сумка(TADP)начинать。 Другая ситуация — вручную установить и запустить OpenCV из Android Требуется для каждого компонента. Если вы ранее установили Android среде разработки, этот вариант можно выбрать. Мы рассмотрим следующие темы:
NVIDIA Опубликовано TADP, так что подготовка среды разработки Android становится гладкой.
NVIDIA Опубликовано TADP 3.0r4 версия для поддержки Android SDK(23.0.2),NDK(r10c)и OpenCV for Tegra 2.4.8.2, этот общепринятыйиз OpenCV4Android SDK, пройдено Tegra Были расширены конкретные оптимизации.
чтобы получить TADP,Пожалуйста, посетитеэта страницаи следуйте инструкциям, чтобыдлязарегистрироватьсяразвивать ВОЗ; Это бесплатное членство.
После активации членства войдите в систему и получите доступ к своей операционной системе. NVIDIA поддерживает следующие операционные системы:
Сразу для меня,У меня на компьютере установлена Windows 7 64 Кусочек., поэтому от начала существования сейчас все последующие шаги были проверены, и существование существует хорошо в этой операционной системе. Да,Если вы используете другую операционную систему,Я не ожидаю каких-либо серьезных изменений.
для Ubuntu Установка, ТАДП Воля Требуется, чтобы у вас былоroot
привилегия,поэтому Пожалуйста, убедитесь, что у вас есть。
После скачивания установщика TADP запустите его и выполните следующие действия:
tadp_uninstall.exe
。
Иногда деинсталлятор не удаляет все. В этом случае вам необходимо вручную удалить предыдущую папку «Установить из содержимого».
Next
кнопка。
Next
кнопка。
даиз,TADP Все будет скачано и установлено за вас; Однако вам все равно необходимо выполнить некоторую настройку после установки, чтобы убедиться, что все работает правильно.
Если вы хотите использовать это SDK Платформа для Цель запуска эмулятора, необходимо для Установитьиз каждого Android SDK Платформа устанавливает образ системы.
Для этого просто выполните следующие простые шаги:
android-sdk-windows
。
X.X
,дляэмуляторвыбиратьодин个образ системы,Например Образ системы ARM EABI V7a:
Теперь вы можете продолжить тестирование приложения на любом существующем эмуляторе Установить Цельиз.
Вам также необходимо настроить Eclipse так, чтобы оно соответствовало NDK работать вместе, чтобы вы могли запустить его прямо из Eclipse строитьлокальная машинаприложение:
Next
。потому что OpenCV Библиотекадаиспользовать C/C++ писатьиз, поэтому проверьте свою среду да нормально проектировать Шаг да Убедитесь, что вы можете бегатьиспользовать собственный код из Android приложение:
C:\NVPACK\android-ndk-r10c\
),отsamples
документпапкасерединаимпортироватьhello-jni
Примерпроект,Сразу Нравитсяимпортироватьлюбойдругой Android То же, что проект.HelloJni
проект。hello-jni
; По умолчанию он должен называться дляthis.hello-jni
проект。 существоватьконтекстменюсередина,выбиратьбегатьдля | Android приложение。существуют консольные выводы,Должно быть.so
документизсписок; это NDK использоватьприложение бинарный интерфейс(ABI)строитьиз Локальный обмен Библиотека,Библиотека точно определила машинный код по внешнему виду.
Android NDK поддерживает другую архитектуру. По умолчанию,еслисуществоватьapplication.mk
документсередина指定Понятно.so
,кроме MIPS и x86 Кроме того, также будет ARM EABI генерировать.so
。 Мы «Волясуществовать» обсудим эту тему далее в этой главе.
Если все пойдет хорошо, ваш эмулятор должен иметь следующий код:
Приложение очень простое,даа хорошо с контрольно-пропускного пункта,это Можеткпроверятьтыда否Можеткот Android Приложение вызывает собственный код.
по сути,тысуществоватьэмулятор屏幕начальствосмотретьприезжатьиздаотлокальная машинакод返回и Зависит от Android существует отображение фрейма в виде строки в текстовом виде.
Чтобы скачать OpenCV Вручную установку OpenCV для Android, на вашем компьютере могут быть следующие компоненты:
Вы можете выполнить действия по установке вручную.,Чтобы убедиться, что все необходимые компоненты готовы и исправны.,к便начинатьиспользовать OpenCV развивать Android приложение。
Вы можете начать сэта страницаскачать Применимо ктыиз OS из JDK Установщик.
Еще один очень хороший вариант да Android Studio。 Вы можете начать сэта страницаскачать Android Studio。 пожалуйста Уведомление,Android Studio и Android SDK Bundle существуют вместе, поэтому, если вы используете эту опцию, вам не нужно ее устанавливать. Кроме того, вы можете пропустить Eclipse и ADT из Установить,и Уведомлениеот Android Studio 1.3 начинать; Вы также найдете пары NDK извстроенныйподдерживать。
Чтобы загрузить и установить Android SDK, выполните следующие действия:
.exe
документ。.exe
документкзапускать Установитьпрограмма,Затем следуйте инструкциям на экране.для OpenCV 2.4。x
,предположениеиспользовать Eclipse 3,7 (Индиго) или Eclipse 4.2(Juno); Вы можете начать с Eclipse изофициальный сайтскачатьтывыбиратьиз Версия。
Предполагая, что вы скачали Eclipse, вы можете скачать его, выполнив следующие действия. Android развиватьперсоналинструмент(ADT)и C/C++ развиватьинструмент(CDT)плагин:
ADT Plug-in
,Затем скопировать и вставить этот URL,существоватьРасположениеПолесередина.Next
。Next
。Next
。в соответствии сиз Требоватьдля C++ развивать Андроид, нужно установить Android NDK。
Не обязательно существовать использовать Android NDK во всех случаях. Как человек, занимающийся развитием, вам необходимо найти баланс между улучшениями производительности, которые приносят существующиеиспользовать собственные API, и сложностью, которую они привносят.
существуем из-за случая,потому чтоOpenCV
Библиотекадаиспользовать C/C++ писатьиз, поэтому нам, возможно, придется использовать NDK。 Однако именно потому,что программисты предпочитают использовать C/C++ писатькодиспользовать NDK 。
Вы можете выполнить следующие действия. Скачать Android NDK:
После завершения загрузки вам необходимо выполнить следующие шаги для настройки NDK:
Перейдите к NDK Папка загрузки.
пункт Вдохновите известных ученых интерпретировать сюжет。
Переименуйте и переместите извлеченную папку; я Воляndk
документпапкасказатьдля<ndk_home>
。сейчассуществовать,ты Можеткиспользовать NDK Давайте построим проект.
Если вы хотите из командной строки для сборки,нонуждаться Воля<ndk_home>
документпапка(существоватьяизслучайдляC:/android/android-ndk-r10d
)добавить вприезжатьPATH
переменные средысередина. для Окна, пожалуйста, откройте CMD。 входитьк Вниз Заказ,и Воляndk
Оглавлениезаменятьдлятыиз Оглавление:
set PATH=%PATH%;c:/android/android-ndk-r10d
Чтобы проверить NDK из Конфигурация верна, пожалуйста, перейдите в каталог проживания, содержащий проект. для Будьте проще,ты Можетксуществоватьhello-jni
Примерпроект Тест на。 ты Можетксуществовать<ndk_home>/samples/
Найдите нижеприезжатьэто。
проходитьвыполнить командуcd <your_project_directory>/
Даже改Оглавление。 Выполните следующую команду:
ndk-build
Как показано в выводе консоли,расширениедля.so
издокументдаэтотпроектсерединаиспользоватьиз C/C++ Исходный код из компиляции Версия:
Если вы предпочитаете от Eclipse строить, что удобнее, надо сказать Eclipse существовать哪里Можеткпопытаться найтиприезжать NDK,к便Можеткстроитьприложение:
<ndk_home>
Оглавление。Next
。<ndk_home>/samples/
импортироватьhello-jni
Примерпроектделатьдля Android проект。hello-jni
проект。существовать控制台середина,ты Волясмотретьприезжать.so
документизсписок,Этидокументдаэтотпроектизскомпилировано C++ часть. Однако если вы отимпортируете проект Открыть любой C/C++ В документе, который вы, Воля, видите, приезжать много выделенных ошибок. Вам просто нужно сделать кое-что и CDT шаги, связанные с подключением:
<ndk_home>/platforms/android-9/arch-arm/usr/include<ndk_home>/sources/cxx-stl/gnu-libstdc++/include<ndk_home>/sources/cxx-stl/gnu-libstdc++/libs/armeabi-v7a/include
<ndk_home> /platforms/android-9/arch-arm/usr/include
<ndk_home>/sources/cxx-stl/gnu-libstdc++/4.6/include
<ndk_home> /sources/cxx-stl/gnu-libstdc++/4.6/libs/armeabi-v7a/include
Next
。 Eclipse Воля перестроит проект и снимет Eclipse Удалить все синтаксические ошибки в .
дляспособныйсуществоватьтыиз Android На устройстве используйте собственный код (C/C++) OpenCV коллекция, которую нужно установить OpenCV4Android СДК, это OpenCV отчасти может существовать Android работать в операционной системе.
2.4.10
。
C:\opencv\
。
Уведомление
强烈предположениеиспользоватьнет пробеловизпуть,к避免ndk-build
出сейчаслюбой问题。
Независимо от того, что вы даиспользовать TADP Выполните «Полное Установление», а также следуйте инструкциям по настройке, существующим на этом этапе. На этом этапе вы все должны улучшить визуальное восприятие. Android Все компоненты необходимые для приложения.
Прежде чем продолжить наш первый пример, давайте уточним NDK из Как работать. привычный Android NDK изосновы и привыкание к нимиспользовать это всегда хорошая идея, потому что для этого Воля становится для насиспользовать OpenCV развивать Android приложениеизкраеугольный камень。
Если вы решите использовать командную строку, скомпилируйте Android приложениеизлокальная машиначасть,тогда должениспользоватьndk-build
инструмент。 ndk-build
инструментна самом деледаодин个Скрипт,Именно Волязание отвечает за следующие скрипты сборки:
Кромеndk-build
инструментснаружи,Вам также следует познакомиться с некоторыми основными компонентами привычного друга.,Чтосерединавключать:
Java и собственный вызов:Android приложениедаиспользовать Java писать, после компиляции исходного кода байт-код для преобразуется так, что Android OS существовать Dalvik или Android бегатьчас(ART)Внизбегатьвиртуальная машина。
Пожалуйста, проверьте выполнение собственного кода приложения на виртуальной машине Dalvik.
использовать при реализации метода из в машинном коде,отвечатьиспользоватьnative
Ключевые слова。
Например,Вы можете объявить функцию «Воля умножить два числа»,И поручите компилятору сделать его нативным:
public native double mul(double x, double y);
Локальный обмен Библиотека:NDK использоватьрасширение.so
строить Эти Библиотека。 Как следует из названия,Они являются общими и ссылками из.
Собственная статическая библиотека:NDK такжекрасширение.a
Приходитьстроить Эти Библиотека; Эта добрая библиотека на самом деле дасуществует компоновку во время компиляции.
Родной интерфейс Java(JNI):существоватьиспользовать Java писать Android приложение когда вам нужен способ Воля позвонить в загрузку приезжать с C/C++ написать из библиотеки, JNI Пригодится.
приложение бинарный интерфейс(ABI):Интерфейсопределение Понятноприложениекомпьютерный кодиз Появление,потому чтодляты Можетксуществоватьдругойизкомпьютерная архитектурабегатьприложение。 По умолчанию НДК для ARM EABI Создайте код. Но да, вы также можете выбрать желаемое для MIPS или x86 строитьиз。
Android.mk
:Воляэтотдокумент Видетьдля Maven Создать сценарий или лучше из Makefile,Должендокументинструктироватьndk-build
Модули, связанные со скриптамиизопределениеиимя,Скомпилируйте необходимый исходный документ,И вам нужна ссылка из Библиотека. изучитьиспользовать Этот документ очень важен,Мы вернемся позже с дополнительной информацией.
Application.mk
:создаватьэтотдокументда Может选из,использовать Всписоктыизприложениенеобходимыйизмодуль。 Эта информация может быть сгенерирована с использованием конкретной Целевой архитектуры, цепочки инструментов и стандартных Библиотек. ABI。
Рассмотрите эти компоненты проживания,Вы можете Подвести итоги для Android разработать собственное приложение из общего процесса,Как показано ниже:
Android.mk
документ Приходитьопределениетыизмодуль,Список исходных кодов для компиляции документа,И перечислить ссылки из Библиотека.Application.mk
; Это необязательно из.Anrdoid.mk
документкопироватьприезжатьпроектпутьсерединаизjni
документпапка Вниз。ndk-build
инструмент Волякомпилировать.so
и.a
Библиотека,тыиз Java код Воля被компилироватьдля.dex
документ,Весь контент упакован в Воля, существует в одном APK-документе.,Подготовить Установить。Когда вы развиваетесь, у вас есть местное одобрение Android приложениечас,тынуждатьсяпривычныйиспользовать NDK нетипичный Android приложениеиз общей структуры.
Обычно вы Android Приложение имеет следующую структуру клипа документа. проектroot
документпапкаиметьк Внизребенок Оглавление:
jni/
libs/
res/
src/
AndroidManifest.xml
project.properties
Здесь и документальный клип, связанный с NDK, следующий:
jni
документпапка Воля Сумка含приложениеизлокальная машиначасть. Другими словами, это да имеет NDK строить Скрипт(НапримерAndroid.mk
иApplication.mk
)из C/C++ Исходный код, необходимый для создания собственной библиотеки.
libs
документпапка Воля Сумка含локальная машина Библиотека。
Уведомление
NDK строитьсистемануждатьсяAndroidManifest.xml
иproject.properties
документ Приходитькомпилироватьприложениеизлокальная машиначасть. Поэтому, если какой-либо из этих документов отсутствует, необходимо сначала скомпилировать Java код, а затем скомпилировать C/C++ код.
Android.mk
существовать本节середина,Я Воля описывает грамматику стройдокумента. как упоминалось ранее,Android.mk
на самом деледа GNU Makefile фрагмент, система сборки проанализирует его, чтобы изучитьсуществоватьпроект в build. Документ «Грамматика» предпочтительнее вашего модуля определения. Модуль — это одно из следующих:
тыуже经использоватьndk-build
Приходитьстроитьhello-jni
проект,поэтомупозволятьнассмотретьодин Вниз ДолженпроектAndroid.mk
документизсодержание:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := hello-jni
LOCAL_SRC_FILES := hello-jni.c
include $(BUILD_SHARED_LIBRARY)
Теперь существуют, лучше вводить эти строки одну за другой:
LOCAL_PATH := $(call my-dir)
:здесь,Скриптопределение Понятноодин个имядляLOCAL_PATH
изпеременная,ипроходить调использоватьmy-dir
функциянастраивать Чтоценить,Эта функция возвращает текущий рабочий каталог.include $(CLEAR_VARS)
:существоватьэтот行середина,Скрипт Сумка含另один个имядляCLEAR_VARS
из GNU Makefile,использовать ВПрозрачныйвсе局部переменная-кLocal_XXX
началоизпеременная,ноLOCAL_PATH
кроме。 Это необходимо потому, что для построениядокументасуществовать анализируется в едином контексте выполнения, где все переменные объявлены как Полные локальные переменные.
LOCAL_MODULE := hello-jni
:существоватьздесь,Скриптопределение Понятноодин个имядляhello-jni
измодуль。 должно быть определеноЗначение LOCAL_MODULE
переменная,И переменная да уникальна из,к标识Android.mk
серединаизкаждыймодуль。
Уведомление
строитьсистема Волясуществоватьтыопределениеизмодульсерединадобавить вlib
префикси.so
назад缀。 существовать Примерслучай,генерироватьиз Библиотека Волябыть названнымдляlibhello-jni.so
。
LOCAL_SRC_FILES := hello-jni.c
:Как следует из названия,ты Волясуществоватьодин个модульсерединасписоквсенуждатьсястроитьи Собратьизисточникдокумент。
Уведомление
Вы указываете только исходный документ, но не документ заголовка; Система сборки отвечает за расчет зависимостей.
include $(BUILD_SHARED_LIBRARY)
:здесьвключать另один个 GNU Makefile,это Воля收集тысуществоватьнаконецодин个include
После команды определите всю информацию и определите, что строить и как строить модуль.
существоватьразвиватьсредазапускатьибегатьи且иметь适когдаиз NDK Предыстория случая. Я могу начать собирать информацию о том, как существует Android приложениесерединаиспользовать OpenCV Библиотекаиз Полныйпейзаж。
Применимо к Android из OpenCV Поддерживается через местный API и Java Упаковка API получить доступ к его функциям. для родного API,ты Воляиспользовать Android NDK Определение этой книги — это библиотека, и она включена для вашего использования. OpenCV Библиотека。 Тогда Вам Воляиспользовать Родной интерфейс Java(JNI)от Java Код вызывает собственную Библиотеку.
另один个выбиратьдаиспользоватьобщепринятыйиз Java импортироватьпрямойсуществовать Java кодсерединаиспользовать OpenCV Java Упаковкаустройство。 Воля произойдет изда, Ява Упаковкаустройство Воляиспользовать JNI Воля вашего вызова, загрузите эту машину OpenCV Библиотека。
Конечно, это зависит от того, какой стиль вы выберете. Но да, вы должны понимать, что использование собственных вызовов можно сократить. JNI Накладные расходы, но требует дополнительной работы. С другой стороны, используйте Java Упаковочная машина может потребовать меньше работы по программированию и вызвать больше JNI накладные расходы.
Рассмотрим такую ситуацию: вы обрабатываете видеокадры или кадры и зображение, и существуете вы изалгоритм, вы Воля звоните нескольким OpenCV функция. В данном случае лучше писать, чтобы все эти функции вызывались из родной Библиотеки. существоватьтыиз Android В приложении можно использовать только JNI Позвоните, чтобы получить доступ к этой родной Библиотеке.
HelloVisionWorld
Android приложениеМы Волястроить мы первые Android приложение, чтобы откамера в режиме реального времени получала кадры предварительного просмотра и использовала OpenCV из Java камера API На экране существования Полный отображается предварительный просмотр.
После да Создать проект в Затмениеиз шагов:
HelloVisionWorld
。
Next
。
activity_hello_vision
измакет Воля ЧтоимядляHelloVisionActivity
。
OpenCV
Библиотекапроектимпортироватьприезжатьтыиз工делать区середина. Навигацияприжатьездокумент | импортировать | Существующий Android Код прибытия на рабочее местосередина.
root
Оглавление。 Отменить выбор всехПримерпроект,тольковыбиратьOpenCV Library
,ЗатемщелкнутьFinish
:
之назадда Создайте проект в Android Studio.изшаг:
HelloVisionWorld
,и Волядомен компаниинастраиватьдляapp0.com
。11
。HelloVisionActivity
。OpenCV
делатьдля Зависимостидобавить вприезжатьтыизпроект,пожалуйста Перейдите кдокумент | Новый | импортироватьмодульи<OpenCV4Android_Directoy>\sdk\java
。 Затем,щелкнутьOK
。 На данный момент это зависит от Android SDK Установив компонент, вы можете столкнуться с некоторыми проблемами при посещении. Android Studio Воля предлагает ссылку для быстрого исправления этой ошибки, которую легко исправить.F4
。+
кнопка,Затемвыбиратьмодуль Зависимости。В дальнейшем, независимо от того, какую IDE вы выберете, вы сможете выполнить следующие шаги:
Открытьlayout
документиверно Чторуководитьредактироватьк匹配к Внизкод. Мы добавили OpenCV пространство имен и определение Java Схема обзора камеры:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:opencv="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="com.example.hellovisionworld.HelloVisionActivity" >
<org.opencv.android.JavaCameraView
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:visibility="gone"
android:id="@+id/HelloVisionView"
opencv:show_fps="true"
opencv:camera_id="any" />
</RelativeLayout>
Скачать пример кода
Вы можете начать сэта страницаскачатьот Покупка аккаунтаизвсе Packt Книги из примера кода документа. еслитысуществоватьдругой地方购买Понятноэтот书,но Можеткдоступэта страницаируководитьзарегистрироваться,Для того, чтобы Волядокумент будет отправлен вам на электронную почту.
потому что Мы Воляиспользоватьоборудованиекамераруководить,поэтомунаснуждатьсясуществоватьAndroidManifest
документсерединанастраиватьодин些权限:
</application>
<uses-permission android:name="android.permission.CAMERA"/>
<uses-feature android:name="android.hardware.camera" android:required="false"/>
<uses-feature android:name="android.hardware.camera.autofocus" android:required="false"/>
<uses-feature android:name="android.hardware.camera.front" android:required="false"/>
<uses-feature android:name="android.hardware.camera.front.autofocus" android:required="false"/>
существоватьAndroidManifest
документсередина隐藏标题исистемакнопка:
<application
android:icon="@drawable/icon"
android:label="@string/app_name"
android:theme="@android:style/Theme.NoTitleBar.Fullscreen" >
Нам нужно существование, чтобы создать из инициализированной активности OpenCV Библиотека。 дляэтот,насиспользовать OpenCV Manager Serviceuse асинхронно инициализируется для доступа к внешнему Установитьсуществовать Цель из OpenCV Библиотека。 Сначала нам нужно существование Воли, чтобы использовать эмулятор при установке. OpenCV Manager。 дляэтот,пожалуйстасуществовать Заказ提示符Внизиспользоватьadb install
Заказ:
adb install <OpenCV4Android SDK_Home>\apk\OpenCV_2.4.X_Manager_2.X_<platform>.apk
использовать тебя OpenCV Установитьдокументпапказаменять<OpenCV4Android SDK_Home>
,использоватьapk
документпапкасерединаиз Можетиспользовать Версиязаменятьapk
имясерединаизX
。
для<platform>
,пожалуйстаиспользовать Вниз表в соответствии сэмуляторначальство Установитьизобраз системывыбиратьхотеть Установитьизплатформа:
Аппаратная платформа | имя пакета |
---|---|
Armeabi-v7a (ARMv7-A + неон) | OpenCV_2.4.X_Manager_2.X_armv7a-neon.apk |
armeabi(ARMv5,ARMv6) | OpenCV_2.4.X_Manager_2.X_armeabi.apk |
Интел х86 | OpenCV_2.4.X_Manager_2.X_x86.apk |
MIPS | OpenCV_2.4.X_Manager_2.X_mips.apk |
существуют при тестировании приложения на реальном устройстве,Воля показывает сообщение,Требоватьтыот Google Play скачать OpenCV Менеджер,поэтомупожалуйстащелкнутьдаи检查Чтоподдерживатьиз OpenCV версию, чтобы вы могли загрузить ее посредством асинхронной инициализации.
существоватьActivity
середина,определение следующее и зафиксировано соответственно импортировать:
//A Tag to filter the log messages
private static final String TAG = "Example::HelloVisionWorld::Activity";
//A class used to implement the interaction between OpenCV and the //device camera.
private CameraBridgeViewBase mOpenCvCameraView;
//This is the callback object used when we initialize the OpenCV //library asynchronously
private BaseLoaderCallback mLoaderCallback = new BaseLoaderCallback(this) {
@Override
//This is the callback method called once the OpenCV //manager is connected
public void onManagerConnected(int status) {
switch (status) {
//Once the OpenCV manager is successfully connected we can enable the camera interaction with the defined OpenCV camera view
case LoaderCallbackInterface.SUCCESS:
{
Log.i(TAG, "OpenCV loaded successfully");
mOpenCvCameraView.enableView();
} break;
default:
{
super.onManagerConnected(status);
} break;
}
}
};
ДаженовыйonResume
Активностьметод обратного вызовакнагрузка OpenCV Библиотека и исправьте соответственно импортировать:
@Override
public void onResume(){
super.onResume();
//Call the async initialization and pass the callback object we //created later, and chose which version of OpenCV library to //load. Just make sure that the OpenCV manager you installed //supports the version you are trying to load.
OpenCVLoader.initAsync(OpenCVLoader.OPENCV_VERSION_2_4_10, this, mLoaderCallback);
}
тыиз Активностьнуждаться实сейчасCvCameraViewListener2
,Талантот OpenCV Вид камеры получает кадры камеры:
public class HelloVisionActivity extends Activity implements CvCameraViewListener2
Исправьте ошибку импортирования соответствующим образом и вставьте нереализованный метод в свою активность.
существоватьonCreate
Активностьметод обратного вызовасередина,Нам нужен вид камеры Воля OpenCV настройкидля видимого,А Воля, ваша регистрация активности для Воля, обрабатывает кадры камеры из объекта обратного вызова:
@Override
protected void onCreate(Bundle savedInstanceState) {
Log.i(TAG, "called onCreate");
super.onCreate(savedInstanceState);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
setContentView(R.layout.activity_hello_vision);
mOpenCvCameraView = (CameraBridgeViewBase) findViewById(R.id.HelloVisionView);
//Set the view as visible
mOpenCvCameraView.setVisibility(SurfaceView.VISIBLE);
//Register your activity as the callback object to handle //camera frames
mOpenCvCameraView.setCvCameraViewListener(this);
}
Последний этап – получение кадров с камеры. дляэтот,пожалуйста Даже改onCameraFrame
метод обратного вызоваиз实сейчас:
```java
public Mat onCameraFrame(CvCameraViewFrame inputFrame) {
//We're returning the colored frame as is to be rendered on //thescreen.
return inputFrame.rgba();
}
```
![Creating a project in Android Studio](https://img-blog.csdnimg.cn/img_convert/e65df8cd175c80ce60243e2f738b739e.png)
К настоящему моменту вы должны были проверить свое первое воспринимаемое зрение. Android приложение。 существования В этой главе вы узнали, как использовать TADP проходить OpenCV настраивать Android улучшить среду или применить ручное решение для обновления существующей среды.
К тому же ты уже учишься NDK из Основы и как это работает. Наконец, вы изучили, как использовать OpenCV Режим «Вид с камеры» захватывает кадры с камеры и отображает их на экране вашего устройства. Этот пример Воля станет для нас основой для реализации более интересных идей.
существовать本главасередина,ты Воляузнать, каксуществовать OpenCV серединахранилищеивыражатьизображение,И как использовать это представление для достижения интересных изалгоритмов.,Этиалгоритм Воля Усиливатьизображениеиз Появление。
Мы, Воля, сначала объясним, как обозначаются цифры и изцветовое изображение. космос, исследовать OpenCV середина重хотетьизMat
добрый。
Затем,Мы Воля шаг за шагом от мобильной картинки Библиотека загружаем изображение и Воля его отображение существует из операций на экране вашего устройства,Независимо от разрешения изображения.
наконец,Ваша гистограмма Воляучитьсяизображение,И как рассчитать ииспользовать их для Усиления изображения (будь то черно-белое и зображение или цветное изображение).
Наша Волясуществовать В этой главе рассматриваются следующие темы:
Где бы мы ни существовали, вокруг нас можно найти фотографии приезжающих; поэтому,Если мы хотим автоматически понимать,Обработка и анализ этих изображений,Затем очень важно изучить изображение и отображение цветов.
Мы живем в непрерывном мире,поэтомухотетьсуществовать Дискретныйизчисло字传感устройствосередина捕获сцена,Сразу необходимо сделать дискретное отображение пространства (макета) и интенсивности (информации о цвете).,Так что воля реального мира и хранилища данных существует и зображение.
2D числаизображениеD(i, j)
отлевыйначальствоначинатьпредставлятьсуществоватьпо номеру строкиi
и Номер столбцаj
выражатьизпиксельиз传感устройство响отвечатьценить,рогдляi = j = 0
。
для означает цвет,Цифровое изображение обычно содержит несколько каналов для определения интенсивности каждого пикселя (цвета). использовать самое широкое цветовое представление для одного канала и изображения,Также известен как оттенки серого и зображение.,где каждому пикселю присваивается оттенок серого в зависимости от его интенсивностиценитьдля: ноль да черный,Максимальная интенсивность белого цвета.
еслииспользоватьот 0 приезжать2^8 - 1
изценитьизбез подписи字符выражатьглубина цвета信息,нона пиксель Можеткхранилищеот от 0 (черный) до 255 (белый) изсилоценить.
В дополнение к отображению цветов в оттенках серого,И настоящее отображение цветов,где цвет представлен тремя каналами вместо одного,И пиксель ценить становится за три элемента (красный,зеленый и синий) и кортеж. существуют В этом случае,Цвет представляет собой линейную комбинацию трех каналов ценообразования.,Причем изображение рассматривается для трех двухмерных плоскостей.
иногда,добавить в Понятноимядля Alpha Четвертый канал использует букву В для представления прозрачности цвета. существуют В этом случае,изображение Воля рассматривается как четыре двумерные плоскости.
и RGB Выразить сравнение, рассмотреть изцветовое пространстваи людям добрый понимание и восприятие цветов более актуально. этодаХюэ,Насыщенностьиценить(HSV)цветовое пространство。
Каждый размер цвета можно понимать следующим образом:
H
):этодасам цвет,красный,Синий или зеленый.S
):это测量цветизчистота; Например, это темно-красный или темно-красный? Только представьте, насколько белый цвет скрывает цвет.V
):этодацветизяркость,Также называется яркостью.наконецхотеть考虑изизображениедобрыйформададвоичныйизображение。 Это двумерный массив пикселей. Но да, каждый пиксель можно хранить только ноль или одно изценить. этот种добрыйформаиливыражать形式для解决Видеть觉问题(Напримеробнаружение края)很重хотеть。
Иметь двумерный массив пикселей и три двумерные плоскости для представления каждой единицы, где каждая единица представляет собой пиксель. RGB В случае цветового пространства из, оно содержит цвет по интенсивностиценить, в случае существования оно содержит Хюэ, насыщенность ценить. HSV цветовое пространствоизразмер,Воляизображениеуменьшить масштабдлячислоценитьматрица。 потому что OpenCV основное внимание уделяется обработке, манипулированию и зображению, поэтому вам нужно учиться в первую очередь да OpenCV 如何хранилищеииметь дело сизображение。
Mat
добрыйсуществоватьиспользовать OpenCV развивать Видеть觉感知приложениечас,Воляиспользоватьиз最重хотетьиз Базовыйчисло据结构даMat
добрый。
Mat
добрыйвыражатьn
维密集число字одинрядили多рядчисло组。 по сути,еслитыиспользоватьMat
добрыйвыражать灰степеньизображение,ноMat
объект Волядахранилище Интенсивность пикселейценитьиз二维число组(иметьодин个ряд)。 еслииспользоватьMat
добрыйхранилище Полныйцветизображение,ноMat
объект Воляда Имеет три каналаиз二维число组(один个рядиспользовать Вкрасныйсила,Один канал окрашен в зеленый цвет,Один канал использует синий цвет),и且такой же适использовать HSV цветовое пространство.
любой Java добрыйодин样,Mat
добрыйиметь构造устройствосписок,И существовать большую часть времени,Конструктора по умолчанию сразу достаточно. Но да,существоватьнекоторыйдругойслучай,Вы можете использовать определенный размер,добрыйформаирядчисло ПриходитьинициализацияMat
объект。
существуют В этом случае,Можно использовать следующие конструкторы:
int numRow=5;
int numCol=5;
int type=org.opencv.core.CvType.CV_8UC1;
Mat myMatrix=newMat(numRow,numCol,type);
Этот конструктор принимает три целочисленных параметра:
int Rows
:новыйматрица行изчисло量int Cols
:новыйматрица列изчисло量int type
:новыйматрицадобрыйформадля Понятно指定Mat
добрыйхранилищеиздобрыйформак及有多少个ряд,OpenCV длятыпоставлять ПонятноCvType
добрыйиstatic int
Поле,и имеет следующее соглашение об именах:
CV_[данные добрый размер шрифта, 8 | 16 | 32 | 64] [Целое число со знаком, без знака и число с плавающей запятой, S | U | F][количество каналов, C1 | C2 | C3 | C4]
Например,ты Волядобрыйформа参число指定дляorg.opencv.core.CvType.CV_8UC1
; Это означает, что один канал матрицы Воляпрохождения занимает 8 Кусочек беззнакового символа интенсивности цвета. Другими словами, эта матрица интенсивности Воляхранилище для от 0 (черный) до 255 (белый) и оттенки серого и зображение.
Mat
操делатьВ дополнение к представлению в OpenCV Библиотека,ты还нуждатьсяпривычный МожетксуществоватьMat
объектначальство执行изодин些Базовый操делать。
Вы можете выполнять самые основные операции — доступ на уровне пикселей.,чтобы получить пиксель ценить,无论тыизцветовое пространствода в оттенках серого также да Полный RGB。 Предположим, у вас есть 1 глава,“Сразунить”изприложение,и且ужезапускатьибегать,ты Можетк回想起существоватьonCameraFrame()
метод обратного вызовасередина,настолькосуществоватьиспользоватьinputFrame.rgba()
Поиск метода Полныйцветкамерарамка。
Используя рамку камеры, мы можем получить доступ к пикселюценить с помощью следующего кода:
@Override
public Mat onCameraFrame(CvCameraViewFrameinputFrame) {
Mat cameraFram=inputFrame.rgba();
double [] pixelValue=cameraFram.get(0, 0);
double redChannelValue=pixelValue[0];
double greenChannelValue=pixelValue[1];
double blueChannelValue=pixelValue[2];
Log.i(TAG, "red channel value: "+redChannelValue);
Log.i(TAG, "green channel value: "+greenChannelValue);
Log.i(TAG, "blue channel value: "+blueChannelValue);
return inputFrame.rgba();
}
Лучше наш Просматривать важно из нескольких строк, остальное на самом деле довольно просто:
double [] pixelValue=cameraFram.get(0, 0);
существоватьэтотодин行середина,нас调использоватьget(0,0)
функцияи Воля Чтопередача给行ииндекс столбца; существуют В этом случае,Это верхний левый угол пикселей.
пожалуйста Уведомление,get()
метод返回один个пара精степеньчисло组,потому чтодляMat
объект最多Можетк容纳Четыре个ряд。
существуем из примера,этода Полныйцветизображение,поэтому Кромеодин个透明степеньряд Alpha(a
)снаружи,на пиксельизкрасный(r
),зеленый(g
)исиний(b
)цветрядизсила Все Волядругой,поэтому Долженметодизимядляrgba()
。
ты Можеткиспользоватьчисло组索引Операция符[]
独立доступкаждыйрядсила,поэтомудлякрасный,интенсивность зеленого и синего,соответственноиспользовать0
,1
и2
:
double redChannelValue=pixelValue[0];
double greenChannelValue=pixelValue[1];
double blueChannelValue=pixelValue[2];
Вниз表список Понятнотынуждатьсяпривычныйиз БазовыйMat
добрый操делать:
Функция | Пример кода |
---|---|
Получить номер канала | Mat myImage; //declared and initialized |
int numberOfChannels=myImage.channels(); | |
Сделайте глубокую копию данных матрицы, существующих изMatobjectiz. | Mat newMat=existingMat.clone(); |
Получить количество столбцов матрицы | Первый метод: Mat myImage //объявлен и инициализирован; |
int colsNum=myImage.cols(); | |
Второй метод: int colsNum=myImage.width(); | |
Третий метод: //И да, это публичная переменная экземпляра. | |
int colsNum=myImage.size().width; | |
Получить количество строк матрицы | Первый метод: Mat myImage //объявлен и инициализирован; |
int rowsNum=myImage.rows(); | |
Второй метод: int rowsNum=myImage.height(); | |
Третий метод: //И да, это публичная переменная экземпляра. | |
int rowsNum=myImage.size().height; | |
Чтобы получить глубину элемента матрицы (для каждого канала издоброго типа): | Mat myImage; //declared and initialized |
int depth=myImage.depth() | |
CV_8U: 8-битное целое число без знака (от 0 до 255). | |
CV_8S: 8-битное целое число со знаком (от -128 до 127). | |
CV_16U: 16-битное целое число без знака (от 0 до 65 535). | |
CV_16S: 16-битное целое число со знаком (от -32 768 до 32 767). | |
CV_32S: 32-битное целое число со знаком (от -2 147 483 648 до 2 147 483 647). | |
CV_32F: 32-битное число с плавающей запятой. | |
CV_64F: 64-битное число с плавающей запятой. | |
Получить общее количество элементов матрицы (количество пикселей в изображении) | Mat myImage; //declared and initialized |
long numberOfPixels=myImage.total() |
существовать本частьсередина,Вы, Воля, научитесь загружать изображение на свой телефон и развлекаться с помощью алгоритма изображения.,Например контраст Усиливать,Сглаживание (удаление шума на изображении) и приложение некоторых фильтров.
Mat
объектпервый Создать новый из Android Проект, начнем. Как вы видели в предыдущей главе, для начала использования OpenCV алгоритм, вам нужно OpenCV Библиотекадобавить вприжатьтыизпроект в:
DarkRoom
。com.example.chapter2.darkroom
。Next
。IODarkRoom
。Next
。Android
Деревоузел,Затем существовать в правой панели,щелкнутьдобавить в。Next
。существоватьэтотпроектсередина,Ваша Воля загружает хранилище вашего телефона,Воля Что Конвертировать для Кусочеккартинаизображение,И показано существование изображения.
Предпочитайте наше приложение отнастройки, событие макета начинается:
<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="horizontal">
<ImageView
android:id="@+id/IODarkRoomImageView"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:src="@drawable/ic_launcher"
android:layout_marginLeft="0dp"
android:layout_marginTop="0dp"
android:scaleType="fitXY"/>
</LinearLayout>
Это да поставляется с изображением в виде простого линейного макета. Далее дана настройка некоторых необходимых разрешений. Если вы хотите начать с SD Изображение загрузки карты, для того, чтобы были настройки соответствующие разрешения Android Предпочитайте свое устройство от внешнего хранилища для чтения и записи.
существовать Контрольный списокдокументсередина,добавить вк Вниз行:
<uses-permissionandroid:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
Это разрешение на запись; Да,Вам также неявно предоставляются разрешения на чтение.,Потому что для него меньше ограничений.
Сейчас мы продолжаем нашу деятельность:
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme">
<activity
android:name=".IODarkRoom"
android:label="@string/app_name"
android:screenOrientation="portrait">
<intent-filter>
<actionandroid:name="android.intent.action.MAIN"/>
<categoryandroid:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
</application>
Это очень простое определение; Но да, без потери общности, моя Воля активна из пределов ориентации для портретной, что означает, что ваша активная Воля не поддерживает альбомный режим. Эта Воля фокусируется на существующем изображении, а не на работе с различными моделями активности. Да,Рекомендую внимательно прочитать эту главу,Воля Долженприложение Расширятьдлятакжеподдерживать Боковая установка Кусочек,Потому что для этого Волядля принесет вам хороший практический опыт.
Для каждой операции в приложении в Воляподдержании нам Воля нужен один пункт меню. Наша первая акция дасуществовать на мобильном Открыть картинку Библиотека, выбрать ьконкретное изображение, для этого вам понадобится существующий документ в дополнении Следующие предметы:
res/menu/iodark_room.xml
<item
android:id="@+id/action_openGallary"
android:orderInCategory="100"
android:showAsAction="never"
android:title="@string/action_OpenGallary"/>
Добавит соответствующее определение строки вприезжатьres/values/strings.xml
:
<stringname="action_OpenGallary">Open Gallary</string>
Мы уже подготовили этот раздел для приложения из определение пользовательского интерфейса,поэтомупозволятьнас继续Чтоназадизкод.
Первый шаг даиспользовать OpenCV Служба менеджера для асинхронной загрузки OpenCV Библиотека, чтобы уменьшить объем памяти, занимаемый приложением. дляэтот,существовать Воляхотетьиспользовать OpenCV алгоритмиз В каждом действии требуется следующий шаблонный код:
private BaseLoaderCallback mLoaderCallback = newBaseLoaderCallback(this) {
@Override
public void onManagerConnected(int status) {
switch (status) {
case LoaderCallbackInterface.SUCCESS:
{
Log.i(TAG, "OpenCV loaded successfully");
} break;
default:
{
super.onManagerConnected(status);
} break;
}
}
};
@Override
public void onResume()
{
super.onResume();
OpenCVLoader.initAsync(OpenCVLoader.OPENCV_VERSION_2_4_8, this, mLoaderCallback);
}
Далее нужно обработать клик пользователя по нашему элементу перед определением изменения:
private static final int SELECT_PICTURE = 1;
private String selectedImagePath;
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
if (id == R.id.action_openGallary) {
Intent intent = newIntent();
intent.setType("https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/opencv-android-prog-example/img/*");
intent.setAction(Intent.ACTION_GET_CONTENT);
startActivityForResult(Intent.createChooser(intent,"Select Picture"), SELECT_PICTURE);
return true;
}
return super.onOptionsItemSelected(item);
}
После того, как пользователь выбрал изображение, из которого загружается изображение, мы Воля выполняем загрузку, а Воля ее отображение существует в результате действия метода обратного вызова:
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (resultCode == RESULT_OK) {
if (requestCode == SELECT_PICTURE) {
Uri selectedImageUri = data.getData();
selectedImagePath = getPath(selectedImageUri);
Log.i(TAG, "selectedImagePath: " + selectedImagePath);
loadImage(selectedImagePath);
displayImage(sampledImage);
}
}
}
Убедитесь, что операция «Открытие» возвращает желаемые результаты (существовать в данном случае для изображения). URI)之назад,нас调использоватьпомощьпрограммаметодgetPath()
Приходить检索нагрузкапутьнеобходимыйиз Форматизизображениепуть。 использовать OpenCV изизображение:
private String getPath(Uri uri) {
// just some safety built in
if(uri == null ) {
return null;
}
// try to retrieve the image from the media store first
// this will only work for images selected from gallery
String[] projection = { MediaStore.Images.Media.DATA };
Cursor cursor = getContentResolver().query(uri, projection, null, null, null);
if(cursor != null ){
int column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
cursor.moveToFirst();
return cursor.getString(column_index);
}
return uri.getPath();
}
После подготовки пути,нас Воля调использоватьloadImage()
метод:
private void loadImage(String path)
{
originalImage = Highgui.imread(path);
Mat rgbImage=new Mat();
Imgproc.cvtColor(originalImage, rgbImage, Imgproc.COLOR_BGR2RGB);
Display display = getWindowManager().getDefaultDisplay();
//This is "android graphics Point" class
Point size = new Point();
display.getSize(size);
int width = size.x;
int height = size.y;
sampledImage=new Mat();
double downSampleRatio= calculateSubSampleSize(rgbImage,width,height);
Imgproc.resize(rgbImage, sampledImage, new Size(),downSampleRatio,downSampleRatio,Imgproc.INTER_AREA);
try {
ExifInterface exif = new ExifInterface(selectedImagePath);
int orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, 1);
switch (orientation)
{
case ExifInterface.ORIENTATION_ROTATE_90:
//get the mirrored image
sampledImage=sampledImage.t();
//flip on the y-axis
Core.flip(sampledImage, sampledImage, 1);
break;
case ExifInterface.ORIENTATION_ROTATE_270:
//get up side down image
sampledImage=sampledImage.t();
//Flip on the x-axis
Core.flip(sampledImage, sampledImage, 0);
break;
}
} catch (IOException e) {
e.printStackTrace();
}
}
Давайте рассмотрим код шаг за шагом:
originalImage = Highgui.imread(path);
Этот метод считывает изображение по заданному пути и возвращает его. этодаHighgui
добрыйсерединаизстатическийстановиться员。
Если вы хотите загрузить цветное изображение, очень важно изучить цветовой канал по порядку. существоватьimread()
изслучай,Раскодируйте изображение Воля нажатием B,G,R Заказать хранилищеиз гл.
Теперь давайте посмотрим на следующий фрагмент кода:
Mat rgbImage=new Mat();
Imgproc.cvtColor(originalImage, rgbImage, Imgproc.COLOR_BGR2RGB);
для Понятно Воляизображениенагрузкадля RGB Кусочек изображения, нам сначала нужно декодировать изображение изображенияот цветового пространства Воли. B,G,R Преобразование в цветовое пространство R,G,B。
первый,нас实例化один个нулевойизMat
объектrgbImage
,ЗатемиспользоватьImgproc.cvtColor()
метод执行цветовое пространствокартографирование. Метод принимает три параметра: исходное изображение, код изображения и карты. Счастливая изда, OpenCV поддерживать 150 Множественные отображения, существующие в ситуациях, которые нам нужны. BGR приезжать RGB картографирование.Теперь давайте посмотрим на следующий фрагмент кода:
Display display = getWindowManager().getDefaultDisplay();
Point size = new Point();
display.getSize(size);
int width = size.x;
int height = size.y;
double downSampleRatio= calculateSubSampleSize(rgbImage,width,height);
потому что лимит памяти,Отображение изображения в исходном разрешении было бы очень расточительно.,иногдаоченькда不Может能из。
Например,еслитыиспользоватьсотовый телефониз 8 Изображение, снятое мегапиксельной камерой, затем предположим 1 Байт глубины цвета,цветизображениеизхранилищерасходыдля8 x 3(RGB) = 24 MB
。
для решения этой проблемы,Рекомендуется изменить размер (уменьшить разрешение) разрешения экрана вашего мобильного телефона. для этого,Сначала мы получаем разрешение дисплея мобильного телефона.,ЗатемиспользоватьcalculateSubSampleSize()
辅助методвычислить Вниз采样соотношение:
private static double calculateSubSampleSize(Mat srcImage, int reqWidth, int reqHeight) {
// Raw height and width of image
final int height = srcImage.height();
final int width = srcImage.width();
double inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
// Calculate ratios of requested height and width to the raw
//height and width
final double heightRatio = (double) reqHeight / (double) height;
final double widthRatio = (double) reqWidth / (double) width;
// Choose the smallest ratio as inSampleSize value, this will
//guarantee final image with both dimensions larger than or
//equal to the requested height and width.
inSampleSize = heightRatio<widthRatio ? heightRatio :widthRatio;
}
return inSampleSize;
}
calculateSubSampleSize()
метод采использовать三个参число:источникизображение,Требуется по ширине и Требуется по высоте,Затем рассчитайте уровень субдискретизации. Сейчас существуют,предпочтение Давайте посмотрим на следующий фрагмент кода:
sampledImage=new Mat();
Imgproc.resize(rgbImage, sampledImage, new Size(),downSampleRatio,downSampleRatio,Imgproc.INTER_AREA);
сейчассуществовать,Мы готовы изменить размер изображенияиз загрузки под размер экрана устройства. первый,нассоздаватьодин个нулевойизMat
объектsampledImage
,сохранить изображение после изменения размера. Затем,нас Воля Чтопередача给Imgproc.resize()
:
Mat
объект,Нам нужно изменить его размерMat
объектSize
объект,Потому что для нас Воля посылает частоту субдискретизацииINTER_LINEAR
,Это соответствует линейной вставкеценитьНужно вставить ценить сюда,Потому что для Воля мы меняем размер изображения (увеличиваем или уменьшаем),И мы хотим, чтобы исходная карта была максимально гладкой.
Если мы уменьшим размер,Вставка ценить Воля определяет, какое Цельизображение пикселя изценитьсуществовать изображениеиз между двумя пикселями. Если мы существуем увеличить размер,Он также вычисляет новые пиксели в Цельизображение и ценить.,А в исходном изображении нет соответствующего пикселя.
В обоих случаях OpenCV существует. Есть несколько вариантов, как вычислить этот добрый пиксель. по умолчаниюизINTER_LINEAR
методпроходитьв соответствии систочник Пиксельи Цель Пиксельизблизость к2 x 2
周围источник Пиксельизценитьруководить线性Взвешенный,рассчитать Цельпиксельценить. или человек,INTER_NEAREST
отисточникизображениесередина最接近из Пиксель获Выбирать Цель Пиксельизценить。 INTER_AREA
选элементна самом деле Воля Цель Пиксель放существоватьисточник Пиксельначальство,Тогда средний охват изпикселить. наконец,нас Можетквыбиратьсуществоватьисточникизображениеиз4×4
周围Пиксельмеждупримерка三次样条,Затемотпримеркаиз样条середина读Выбирать Взаимноотвечатьиз Цельценить; этотдавыбиратьINTER_CUBIC
内Вставлятьметодизрезультат。
Чтобы уменьшить изображение,в целомсуществоватьINTER_AREA
Вставлятьценить Внизсмотреть起Приходить最хороший,И увеличить изображение,в целомсуществоватьINTER_CUBIC
(медленный)илиINTER_LINEAR
(Даже快,но все равно выглядит хорошо) выглядит лучше всего.
try {
ExifInterface exif = new ExifInterface(selectedImagePath);
int orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, 1);
switch (orientation)
{
case ExifInterface.ORIENTATION_ROTATE_90:
//get the mirrored image
sampledImage=sampledImage.t();
//flip on the y-axis
Core.flip(sampledImage, sampledImage, 1);
break;
case ExifInterface.ORIENTATION_ROTATE_270:
//get upside down image
sampledImage=sampledImage.t();
//Flip on the x-axis
Core.flip(sampledImage, sampledImage, 0);
break;
}
} catch (IOException e) {
e.printStackTrace();
}
сейчассуществовать,Нам нужно обработать направление изображения.,и且потому что Активность толькосуществовать работает в портретном режиме, поэтому мы Воля 90 или 270 Степень обработки изображения степени вращения.
существовать旋изменять 90 В данном случае это означает, что вы сделали снимок, когда телефон был в портретной ориентации; наспроходить调использоватьt()
метод Воляизображение逆час针旋изменять 90 степень,кизменять置Mat
объект。
транспонированный результат да исходное изображениеиз зеркала Версия,поэтомунаснуждаться执行另одиншагкпроходить调использоватьCore.flip()
и Воля Чтопередачаприезжатьисточникизображениеи Цельизображениеи调использовать翻изменятькод Приходить指定如何沿垂直轴翻изменятьизображение; 0
выражать围绕 x 轴翻изменять,толькоценить(Например1
)выражать围绕 y 轴翻изменять,грузценить(Например-1
)выражать围绕два个轴翻изменять。
для 270 поворот на градус, что означает, что вы держите телефон вверх ногами, чтобы сделать снимок. Следуем тому же иалгоритму, транспонируем и затем переворачиваем изображение. Да,существовать после транспонирования изображения,Это Воляда вокруг горизонтального направления из зеркала Версия,поэтомунас Воля0
и0
翻изменятькододин起вызов.
Теперь мы готовы отобразить компонент представления:
private void displayImage(Mat image)
{
// create a bitMap
Bitmap bitMap = Bitmap.createBitmap(image.cols(), image.rows(),Bitmap.Config.RGB_565);
// convert to bitmap:
Utils.matToBitmap(image, bitMap);
// find the imageview and draw it!
ImageView iv = (ImageView) findViewById(R.id.IODarkRoomImageView);
iv.setImageBitmap(bitMap);
}
первый,Создаем объект диаграммы Кусочек,Его цветовые каналы в порядке и загрузка изображения цветовых каналов в порядке RGB соответствуют. Тогда.,насиспользоватьUtils.matToBitmap()
ВоляMat
объект Конвертировать для Кусочеккартинаобъект。 наконец,насиспользоватьновыйсоздаватьиз Кусочеккартинаобъектнастраиватьизображение Видетькартина Кусочеккартина。
Мы находимся в одном шаге от изучения изображения содержания и одного из основных методов анализа изображения. Сразуда Рассчитать гистограмму изображения.
Гистограмма да — это распределение интенсивности данного изображения по общему графику с В. как упоминалось ранее,существовать x 轴начальство,绘картина Воляиметь0
к255
в пределах досягаемостиизценить,Это зависит от глубины,и y Ось Воля представляет количество появлений соответствующей интенсивности ценитьиз.
После того, как изображение-гистограмма будет рассчитана и отображена,Вы сразу можете легко получить информацию о контрастности изображения.,силаточка布ждатьизодин些见解。 Фактически, если гистограмму Воли нормализовать так, что она всегда идля 1, вы можете просмотреть гистограмму Воли для функции плотности вероятности и ответить на такие вопросы, как, например, сколько из вероятности да для данной интенсивности, ценить появление существующего изображения, ответить сразуда y Прочтите ось при этом значении интенсивности. На рисунке ниже вы можете увидеть интенсивность проживания для 50 из пикселя появляется существующее изображениеиз слева 5,000 Второсортный:
Прежде чем мы начнем изучать и начинать рассчитывать гистограмму, нам нужно изучить некоторые компоненты и термины, чтобы рассчитать гистограмму:
(0, 255)
,Прямо сейчасвсесила。
Теперь мы готовы показать вам, как использовать OpenCV Библиотека Расчет изображениежистограммы.
Наша Воля продолжает существование, начатое в предыдущем разделе из того же приложения «Строить» из изменений «дасуществовать менюдокумент в добавлении». Добавлен еще один термин меню для запуска расчета гистограммы.
изменятьприезжатьres/menu/iodark_room.xml
документи Открытьэток Сумка含к Внизменюэлемент:
<item
android:id="@+id/action_Hist"
android:orderInCategory="101"
android:showAsAction="never"
android:title="@string/action_Hist">
</item>
Вот и все, что касается изменений пользовательского интерфейса.
существоватьIODarkRoom
В активности нам необходимо обработать пользователя, нажав пункт меню Показать гистограмму.
如ВнизредактироватьonOptionesItemSelected()
метод:
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
if (id == R.id.action_openGallary) {
Intent intent = newIntent();
intent.setType("https://gitcode.net/apachecn/apachecn-cv-zh/-/raw/master/docs/opencv-android-prog-example/img/*");
intent.setAction(Intent.ACTION_GET_CONTENT);
startActivityForResult(Intent.createChooser(intent,"Select Picture"), SELECT_PICTURE);
return true;
}
else if (id == R.id.action_Hist) {
if(sampledImage==null)
{
Context context = getApplicationContext();
CharSequence text = "You need to load an image first!";
int duration = Toast.LENGTH_SHORT;
Toast toast = Toast.makeText(context, text, duration);
toast.show();
return true;
}
Mat histImage=new Mat();
sampledImage.copyTo(histImage);
calcHist(histImage);
displayImage(histImage);
return true;
}
return super.onOptionsItemSelected(item);
}
пожалуйста Уведомление,Если нажат пункт меню «Показать гистограмму»,Сначала мы проверяем, загрузил ли пользователь да изображение.,Если пользователь не загружается,затем покажи дружеское сообщение,Затем Воля Что返回。
Сейчас существует часть гистограммы,Как показано ниже:
Mat histImage=new Mat();
sampledImage.copyTo(histImage);
calcHist(histImage);
displayImage(histImage);
return true;
наспервый制делатьиспользовать户нагрузкаизуменьшить масштабизображениеизкопировать。 Это необходимо, поскольку мы изменили гистограмму, чтобы отобразить гистограмму, поэтому нам необходимо получить копию оригинала. получатькопироватьназад,нас Воля调использоватьcalcHist()
и Воля Чтопередача给новыйизображение:
private void calcHist(Mat image)
{
int mHistSizeNum = 25;
MatOfInt mHistSize = new MatOfInt(mHistSizeNum);
Mat hist = new Mat();
float []mBuff = new float[mHistSizeNum];
MatOfFloat histogramRanges = new MatOfFloat(0f, 256f);
Scalar mColorsRGB[] = new Scalar[] { new Scalar(200, 0, 0, 255), new Scalar(0, 200, 0, 255), new Scalar(0, 0, 200, 255) };
org.opencv.core.PointmP1 = new org.opencv.core.Point();
org.opencv.core.PointmP2 = new org.opencv.core.Point();
int thikness = (int) (image.width() / (mHistSizeNum+10)/3);
if(thikness> 3) thikness = 3;
MatOfInt mChannels[] = new MatOfInt[] { new MatOfInt(0), new MatOfInt(1), new MatOfInt(2) };
Size sizeRgba = image.size();
int offset = (int) ((sizeRgba.width - (3*mHistSizeNum+30)*thikness));
// RGB
for(int c=0; c<3; c++) {
Imgproc.calcHist(Arrays.asList(image), mChannels[c], new Mat(), hist, mHistSize, histogramRanges);
Core.normalize(hist, hist, sizeRgba.height/2, 0, Core.NORM_INF);
hist.get(0, 0, mBuff);
for(int h=0; h<mHistSizeNum; h++) {
mP1.x = mP2.x = offset + (c * (mHistSizeNum + 10) + h) * thikness;
mP1.y = sizeRgba.height-1;
mP2.y = mP1.y - (int)mBuff[h];
Core.line(image, mP1, mP2, mColorsRGB[c], thikness);
}
}
}
calcHist()
методточкадлядвачасть.
Часть 1. Конфигурация гистограммы. Внешний вид. Определение компонентов гистограммы, связанных с:
int mHistSizeNum = 25;
MatOfInt mHistSize = new MatOfInt(mHistSizeNum);
первый,насопределение直方картина箱из个число。 существуют. В данном случае мы изгистограммы Воля имеем 25 коробка. Затем,насинициализацияодин个MatOfInt()
объект,ДолженобъектдаMat
добрыйизребенокдобрый,нотолькохранилище带有直方картина箱числоиз整число。 инициализацияизрезультатдаразмердля1 x 1 x 1 (row x col x channel)
изMatOfInt
объект,Чтосередина保留число字25
。
Нам нужно инициализировать вот так изобъект,Потому что по техническим характеристикам,OpenCV вычислить直方картинаметод采использоватьодин个Mat
объект,Объект сохраняет количество интервалов гистограммы.
Затем,насиспользоватьк Вниз Заказинициализацияодин个новыйизMat
объекткдержать直方картинаценить:
Mat hist = newMat();
на этот раз,Mat
объектизразмердля1 x 1 x nbins
:
float []mBuff = new float[mHistSizeNum];
Напомним, что начало этой главы существовало в середина,Мы посетили отдельные пиксели на изображении. жить здесь,У нас есть та же технология для доступа к ячейкам гистограммы.,и Воляэто们хранилищесуществоватьfloat
добрыйформаизчисло组середина. Здесь мы определяем еще одну составляющую гистограммы, а именно Диапазон. гистограммы:
MatOfFloat histogramRanges = new MatOfFloat(0f, 256f);
насиспользоватьMatOfFloat()
добрый; этодаMat
добрыйизребенокдобрый,Как следует из названия,Он содержит только числа с плавающей запятой.
инициализацияизрезультат Волядаразмердля2 x 1 x 1
изMat
объект,Чтоценитьсоответственнодля0
и256
:
Scalar mColorsRGB[] = new Scalar[] { new Scalar(200, 0, 0, 255), new Scalar(0, 200, 0, 255), new Scalar(0, 0, 200, 255) };
При создании гистограммы для каждого канала мы воляпроходим рисуем линии с соответствующими цветами каналов, чтобы различать каждый канал из гистограммы. насинициализацияодин个Зависит от三个Scalar
объекткомпозицияизчисло组,Долженобъект Толькодаодин个长степень最多для 4 прецизионный массив изпара, представляющий три цвета: красный, зеленый и синий. Инициализируйте две точки, чтобы нарисовать линию для каждого интервала гистограммы:
org.opencv.core.PointmP1 = new org.opencv.core.Point();
org.opencv.core.PointmP2 = new org.opencv.core.Point();
Для нашего поля гистограммы рисуется каждая линия, нам нужно указать толщину линии:
int thikness = (int) (image.width() / (mHistSizeNum+10)/3);
if(thikness> 3) thikness = 3;
использоватьценить0
,1
и2
инициализация三个MatOfInt
объект,Чтобы проиндексировать каждый канал изображения независимо:
MatOfInt mChannels[] = new MatOfInt[] { new MatOfInt(0), new MatOfInt(1), new MatOfInt(2) };
Рассчитайте смещение, чтобы начать рисовать гистограмму:
Size sizeRgba = image.size();
int offset = (int) ((sizeRgba.width - (3*mHistSizeNum+30)*thikness));
предпочтение Переходим ко второй части существования, которая рассчитывает и рисует гистограмму:
// RGB
for(int c=0; c<3; c++) {
Imgproc.calcHist(Arrays.asList(image), mChannels[c], new Mat(), hist, mHistSize, histogramRanges);
Core.normalize(hist, hist, sizeRgba.height/2, 0, Core.NORM_INF);
hist.get(0, 0, mBuff);
for(int h=0; h<mHistSizeNum; h++) {
mP1.x = mP2.x = offset + (c * (mHistSizeNum + 10) + h) * thikness;
mP1.y = sizeRgba.height-1;
mP2.y = mP1.y - (int)mBuff[h];
Core.line(image, mP1, mP2, mColorsRGB[c], thikness);
}
}
Уведомление Из Первое дело Мы можем рассчитывать гистограмму только одного канала за раз. этот Сразудадля什么насдлятри каналабегатьодин个for
циклиз原потому что。 к Вциклизосновная часть,Нет.один步да调использоватьImgproc.calcHist()
,Воля выполняет всю тяжелую работу после прохождения следующих параметров:
Mat
объектизсписок。 Imgproc.calcHist()
вычислитьизображениесписокиз直方картина,существуем из примера,настолькосуществоватьпередачатолько Сумка含один个изображениеизMat
объектсписок。MatOfInt
объект。Mat
объектиспользоватьделатьмаска。 Да,существовать本例середина,Нам нужно вычислить всю гистограмму изображения.,этот Сразудадля什么нас发送один个нулевойизMat
объектиз原потому что。Mat
объект,использовать Вхранилище直方картинаценить。MatOfInt
объект,Используйте В, чтобы сохранить количество ящиков.MatOfFloat
объект,использовать Вдержать Диапазон гистограммы。Теперь существуют мы рассчитали гистограмму,Надо это нормализовать ценить,к便Можетксуществоватьоборудование屏幕начальство显示это们。 Core.normalize()
Можеткк几种другойиз Способиспользовать:
Core.normalize(hist, hist, sizeRgba.height/2, 0, Core.NORM_INF);
Здесь используйте метод даиспользовать входной массив из нормы для нормализации.,Этот пример изгистограммыоценить,и передайте следующие параметры:
Mat
объект。Mat
объект。0
,Потому что для не заменяется наиспользовать.Core.NORM_INF
,это告诉 OpenCV использовать бесконечную норму для нормализации, воля входного массива максимально ценитьнастройки и т. д. alpha Параметры (существовать в данном случае для изображения высоты пополам). Вы можете использовать другую спецификацию, например L2 Нормы L1 спецификация,этотсоответственно Взаимнокогда ВпередачаCore.NORM_L2
илиCore.NORM_L1
。 另снаружи,ты МожеткпроходитьпередачаCore.MINMAX
Приходитьиспользоватьобъем归один化,этот会Готов оценить 归один化 для альфа и бета между параметрами.После стандартизации,нассуществоватьfloat
число组середина检索直方картина箱ребенокценить:
hist.get(0, 0, mBuff);
наконец,насиспользоватьCore.line()
для直方картинасерединаизкаждый箱ребенокрисоватьодин条线:
for(int h=0; h<mHistSizeNum; h++) {
//calculate the starting x position related to channel C plus 10 //pixels spacing multiplied by the thickness
mP1.x = mP2.x = offset + (c * (mHistSizeNum + 10) + h) * thikness;
mP1.y = sizeRgba.height-1;
mP2.y = mP1.y - (int)mBuff[h];
Core.line(image, mP1, mP2, mColorsRGB[c], thikness);
}
КCore.line()
передачак Вниз参число:
Mat
объектPoint
объектPoint
объектScalar
объектКонечный результат Воляда загружает изображение, которое содержит гистограмму для каждого цветового канала:
сейчассуществовать,Вы изучаете гистограмму и как ее рассчитать,Пришло время взглянуть на один из самых распространенных методов: сбалансированную гистограмму. Сбалансированный метод гистограммы с Увеличьте контрастность изображения.,То есть разницу между самыми маленькими максимальной интенсивностью ценить,Так что Усиливать можно смыть детали изображения.
от абстрактной перспективы,Гистограмма сбалансированной функции: найти место для функции,Долженфункция获Выбиратьизображениеиз原始直方картинаи Воля Что Конвертировать дляиметьизображениесилаценитьравномерно распределенныйиз拉伸直方картина,оти Увеличьте контрастность изображения。
на самом деле,Гистограмма «Сбалансированный» не дает полной выходной гистограммы «сбалансированный». Но да,Это обеспечивает хорошее приближение требуемого преобразования.,от И может распределить интенсивность более равномерно в пределах ценить
С самого начала этой книги,На самом деле мы не делаем различий между Воляприложениеизалгоритмприложение в оттенках серого и Полноцветные изображения. Но даа.,Воля Гистограмма сбалансированное приложение В Оттенки серого и зображение Воля ее приложения В Полный Цвет и зображение с различными эффектами.
нас Воляпервыйот Воля直方картинасбалансированныйприложение В灰степеньизображение。
нас Волясуществовать Переднийразвиватьизпроектизбаза础начальство,добавить в Даже多менюэлементк触发изображение Усиливать Функция。
Открытьменюдокументres/menu/iodark_room.xml
,Затемдобавить вновыйизребенокменю:
<item android:id="@+id/enhance_gs"android:title="@string/enhance_gs"android:enabled="true"android:visible="true"android:showAsAction="always"android:titleCondensed="@string/enhance_gs_small">
<menu>
<item android:id="@+id/action_togs"android:title="@string/action_ctgs"/>
<item android:id="@+id/action_egs"android:title="@string/action_eqgsistring"/>
</menu>
</item>
существоватьновыйизребенокменюсередина,Мы добавлено два новых проекта: один для трансформации изображение в оттенки серого, второй член запускает сбалансированную гистограмму.
OpenCV поддерживает несколько преобразований цветового пространства,поэтому Воля Полныйцветизображение Конвертировать для灰степень级необходимыйиз工делать量非常Маленький。
наснуждатьсясуществовать Активностьсередина ДаженовыйonOptionsItemSelected(MenuItem item)
методкиметь дело св соответствии с Внизновыйменюэлементиз操делать,к便Конвертировать для灰степень:
else if (id == R.id.action_togs) {
if(sampledImage==null)
{
Context context = getApplicationContext();
CharSequence text = "You need to load an image first!";
int duration = Toast.LENGTH_SHORT;
Toast toast = Toast.makeText(context, text, duration);
toast.show();
return true;
}
greyImage=new Mat();
Imgproc.cvtColor(sampledImage, greyImage, Imgproc.COLOR_RGB2GRAY);
displayImage(greyImage);
return true;
}
Проверяем, загрузился ли образец изображения,Затем调использоватьImgproc.cvtColor()
А Воля ему передаются следующие параметры:
Mat
объект。Mat
объект。Наконец, мы отображаем изображение в оттенках серого.
нас Даже改ПонятноonOptionsItemSelected(MenuItem item)
методкиметь дело с直方картинасбалансированныйменюэлемент:
else if (id == R.id.action_egs) {
if(greyImage==null)
{
Context context = getApplicationContext();
CharSequence text = "You need to convert the image to greyscale first!";
int duration = Toast.LENGTH_SHORT;
Toast toast = Toast.makeText(context, text, duration);
toast.show();
return true;
}
Mat eqGS=new Mat();
Imgproc.equalizeHist(greyImage, eqGS);
displayImage(eqGS);
return true;
}
Мы Воля еще раз проверяем, является ли пользователь да уже Преобразовать изображение в оттенки серогоизображение。 В противном случае метод выравнивания гистограмм не сработает. Затем,нас调использоватьImgproc.equalizeHist()
и传入два个参число:
Mat
объектMat
объектнаконец,нас调использоватьdisplayImage()
к显示Усиливатьназадизизображение:
Чтобы использовать сбалансированный гистограмму, приходите Усиливать полное цветное изображение и получите тот же эффект.,Прямо сейчас Увеличьте контрастность изображения,наснуждаться Воляизображениеот RGB трансформация пространствадля HSV,Затем Волятакой жеизалгоритмприложение Вполныйи(S)иценить(V)ряд。
Внесенные изменения в меню новый пункт для запуска HSV Улучшения, связанные с:
<item android:id="@+id/action_HSV"android:titleCondensed="@string/action_enhanceHSV"android:title="@string/action_enhanceHSV"android:enabled="true"android:showAsAction="ifRoom"android:visible="true"/>
Вам необходимо овладеть основными навыками дасуществовать на основе каждого канала:
else if (id == R.id.action_HSV) {
if(sampledImage==null)
{
Context context = getApplicationContext();
CharSequence text = "You need to load an image first!";
int duration = Toast.LENGTH_SHORT;
Toast toast = Toast.makeText(context, text, duration);
toast.show();
return true;
}
первый,ДаженовыйonOptionsItemSelected(MenuItem item)
киметь дело сновыйизменюэлемент:
Mat V=new Mat(sampledImage.rows(),sampledImage.cols(),CvType.CV_8UC1);
Mat S=new Mat(sampledImage.rows(),sampledImage.cols(),CvType.CV_8UC1);
инициализациядва个новыйизMat
объекткдержатьизображениеценитьи Насыщенностьряд:
Mat HSV=new Mat();
Imgproc.cvtColor(sampledImage, HSV, Imgproc.COLOR_RGB2HSV);
сейчассуществовать,нас Воля RGB изображение Конвертировать для HSV Цветовое пространство:
byte [] Vs=new byte[3];
byte [] vsout=new byte[1];
byte [] ssout=new byte[1];
for(int i=0;i<HSV.rows();i++){
for(int j=0;j<HSV.cols();j++)
{
HSV.get(i, j,Vs);
V.put(i,j,new byte[]{Vs[2]});
S.put(i,j,new byte[]{Vs[1]});
}
}
Затем,нас逐Пиксельдоступизображениеккопировать Насыщенностьиценитьряд:
Imgproc.equalizeHist(V, V);
Imgproc.equalizeHist(S, S);
调использоватьImgproc.equalizeHist()
к Усиливатьценитьи Насыщенностьряд:
for(int i=0;i<HSV.rows();i++){
for(int j=0;j<HSV.cols();j++)
{
V.get(i, j,vsout);
S.get(i, j,ssout);
HSV.get(i, j,Vs);
Vs[2]=vsout[0];
Vs[1]=ssout[0];
HSV.put(i, j,Vs);
}
}
сейчассуществовать,нас Воля Усиливатьиз Насыщенностьиценитькопировать Вернуться к оригиналуизображение:
Mat enhancedImage=new Mat();
Imgproc.cvtColor(HSV,enhancedImage,Imgproc.COLOR_HSV2RGB);
displayImage(enhancedImage);
return true;
Наконец, мы будем HSV цветтрансформация пространствадля RGB и显示Усиливатьизизображение:
существоватькрасный,Построение гистограммы по зеленому и синему каналам дает разные эффекты.,Сразу, как будто ты настраиваешь Хюэ.
Мы Волядобавить В новом изменяю элемент, который будет выполняться на одном канале или группе каналов. RGB Улучшение:
<item android:id="@+id/action_RGB"android:title="@string/action_RGB"android:titleCondensed="@string/action_enhanceRGB_small"android:enabled="true"android:showAsAction="ifRoom"android:visible="true">
<menu>
<item android:id="@+id/action_ER"android:titleCondensed="@string/action_enhance_red_small"android:title="@string/action_enhance_red"android:showAsAction="ifRoom"android:visible="true"android:enabled="true"android:orderInCategory="1"/>
<item android:id="@+id/action_EG" android:showAsAction="ifRoom"android:visible="true"android:enabled="true"android:titleCondensed="@string/action_enhance_green_small"android:title="@string/action_enhance_green"android:orderInCategory="2"/>
<item android:id="@+id/action_ERG" android:showAsAction="ifRoom"android:visible="true"android:enabled="true"android:titleCondensed="@string/action_enhance_red_green_small"android:title="@string/action_enhance_red_green"android:orderInCategory="3"/>
</menu>
</item>
У вас может быть очень медленный попиксельный доступ.,Особенно когда разрешение высокое. существуют В этом разделе,Мы, Воля, исследуем еще один канал использования изображений из технологий.,Технология работает быстрее,Как показано ниже:
else if(id==R.id.action_ER)
{
if(sampledImage==null)
{
Context context = getApplicationContext();
CharSequence text = "You need to load an image first!";
int duration = Toast.LENGTH_SHORT;
Toast toast = Toast.makeText(context, text, duration);
toast.show();
return true;
}
Mat redEnhanced=new Mat();
sampledImage.copyTo(redEnhanced);
Mat redMask=new Mat(sampledImage.rows(),sampledImage.cols(),sampledImage.type(),new Scalar(1,0,0,0));
этотв重хотетьизодин行даинициализацияredMask
(этотдаодин个Mat
объект),всерядвсенастраиватьдля0
,Кроме первого канала,Нет.один个рядда RGB изображениесерединаизкрасныйряд。
Затем,нас调использоватьenhanceChannel()
метод,И передадим созданную нами копию загруженной маски канала изображения:
enhanceChannel(redEnhanced,redMask);
существоватьenhanceChannel()
методсередина,наспервый Волянагрузкаизизображениекопироватьприезжать另один个Mat
объект:
private void enhanceChannel(Mat imageToEnhance,Mat mask)
{
Mat channel=new Mat(sampledImage.rows(),sampledImage.cols(),CvType.CV_8UC1);
sampledImage.copyTo(channel,mask);
Imgproc.cvtColor(channel, channel, Imgproc.COLOR_RGB2GRAY,1);
Imgproc.equalizeHist(channel, channel);
Imgproc.cvtColor(channel, channel, Imgproc.COLOR_GRAY2RGB,3);
channel.copyTo(imageToEnhance,mask);
}
Да,На этот раз мы передаем маску Воли методу копировать.,Извлеките указанный канал только с помощью.
Затем,нас Волякопироватьизряд Конвертировать для灰степеньцветнулевой间, так что深степеньдля 8 Кусочек,и且equalizeHist()
не подведет。
Наконец, мы будем Что Конвертировать для RGB Mat
объект,Воля Усиливатьизрядкопироватьприезжатькрасный,зеленый и синий,Затемиспользоватьтакой жеизмаска Воля Усиливатьизрядкопироватьприезжатьпередачаиз参число。
Вы можете легко создавать свои собственные маски для использования различных каналов или комбинаций каналов.
приезжатьв настоящий моментдляконец,Вы уже должны знать, как выражать существование в OpenCV. Вы также создали свою собственную темную комнату.,откартина Библиотека Загрузка изображения,Рассчитайте и отобразите его гистограмму,И выполнить гистограмму сбалансированной на разных цветовых пространствах.,к Усиливатьизображениеиз Появление。
существовать Внизодинглавасередина,Мы Воляразвиваем новое изприложение,к利использовать Даже多из OpenCV Алгоритм обработки изображений и компьютерного зрения. Мы воляиспользуемалгоритм для сглаживания и зображения и обнаружения возраста, линий и кругов.
существовать本главасередина,Мы с Воля начинаем реализовывать следующее приложение,Это программный сканер. Это позволяет людям фотографировать квитанцию.,И выполните некоторые преобразования, чтобы он выглядел так, как будто он был отсканирован.
Долженприложение Воляточкадлядваглава。 существовать本главасередина,Мы, Воля, знакомим с двумя важными темами.,Этими темами Воляпомощь мы достигаем конечной Цели.
Первая тема посвящена пространственной фильтрации и ее применению. Вы, Воля, узнаете, как уменьшить шум изображения, также называемый сглаживанием изображения. Кроме того, вы еще и Воляучитьсяиспользовать OpenCV Процесс обнаружения краев изображения (границ объектов) реализован с высокой степенью абстракции различными алгоритмами.
Вторая тема Воля посвящена еще одному известному методу анализа форм, который называется «Преобразование». Хафа。 Вы Воляучитесь в основной идее технологии, которая сделала ее очень популярной, а мы Воляиспользовать OpenCV Реализация для начала воля прямой линии и подгонки круга позволяет получить набор краевых пикселей.
существовать Нет. 2 глава,“приложение 1-Постройте свою собственную темную комнату» мы обсудили, как использовать сбалансированную гистограмму и другие методы Усиливать данное изображение, так что изображение приятнее. Усиливатьдругойцветовое пространствосерединаизизображениеверно比степень。 существовать本节середина,Мы с Волей обсуждаем еще одну технологию Усиливать,Обычно используется в качестве этапа предварительной обработки во многих алгоритмах компьютерного зрения.,Прямо сейчаснулевой间滤波。
существуют До начала реализации концепции,позволятьнаспервый Создать новый из Android приложение。 Мы выполним те же шаги, что и в предыдущей главе; Да,Мы, Воля, перечисляем различные шаги, связанные с присвоением имени.,Согласно этой доброй рекомендации:
SoftScanner
。com.app2.softscanner
。SoftScanner
Прямо сейчас Может。Прежде чем продолжить, убедитесь, что вы можете загрузить библиотеку OpenCV, прочитать и отобразить хранилище существующего изображения на своем телефоне.
Усиление изображенияиз основной цели делает изображение более привлекательным и визуально приемлемым.,И обычно нужно делать изда, чтобы подчеркнуть края.,уменьшать少噪точкаииногда Добавьте эффект размытия。
Эти операции Усиливать, как и многие другие операции Усиливать, могут быть реализованы с применением пространственной фильтрации. Мы используем пространство «использовать существующий проект», чтобы подчеркнуть, что процесс фильтрации происходит на фактическом изображении пикселей, и воля, от которой отличаются идругой фильтры (например, фильтры частотной области). существуя, двигаясь вперед из процесса, мы, Воля, больше не обсуждаем фильтры частотной области, поэтому от настоящего времени мы, Воля, пространственная фильтрацияустройствосказатьдля过滤устройство。
Независимо от того, какой фильтр вы хотите использовать,в целомследоватьиз Воля过滤устройствоприложение Визображениеиз Процесс почтидастандартныйиз。 Короче говоря, с помощью линейного фильтра мы рассматриваем каждый пиксель исходного изображенияиз (обычно Воля его называют пикселем для Цель), а Воля его ценит заменяет для его окружения указанную окрестность из взвешенных и. 之Местоксказатьдля линейного фильтра,да Потому что для Цель пикселя из нового ценится его ближайшие пиксели из линейной комбинации (взвешенной и) из результата.
Взвешенный общий вес определяется ядром фильтра (маской); Для этого нам нужно учитывать размер окрестности. Метод расчета нового Цель пикселя определяет ядро Кусочка так, чтобы центральный вес из Расположениеи Цель пикселей совпадал. Затем мы объединяем взвешенные значения соседних пикселей (включаем Цель-пиксели и соответствующие им веса), чтобы получить Цель-пиксель из новой цены. наконец,Продолжаем повторять этот процесс для каждого пикселя в пикселе.
приложение дискретной формы из линейного фильтра из механизма, также называемого для свертки,иногда Воля过滤устройствоядерныйописыватьдля Ядро свертки。
наконец,Мы можем подвести итог линейному процессу свертки,Как показано ниже:
Фильтрация из первого приложения делает изображение размытым, также известное как сглаживание. Этот процесс приводит к снижению шума. Мы, Воля, представляем три различных метода размытия: среднее, гауссовское и среднеценить.
Проектирование ядра свертки,Воля Цельпиксель изценить заменяет на субатомное соседство изсреднеценить,Можетк得приезжатьсредний фильтр。
размердля3 x 3
нетипичный Ядро сверткиk
Как показано ниже:
Следуйте процедуре иммиграции, упомянутой выше.,каждый Цель Пиксель Воля被Что3 x 3
Районизсреднийценитьзаменять,Изменение размера ядра Воля делает изображение более размытым,Потому что окрестности содержат все больше и больше пикселей.
Обращайтесь с каждым пикселем среди его соседей одинаково,так, чтобы каждый пиксель в окрестности имел одинаковый вес,Прямо сейчасверноновый Цель Пиксельценитьиз影响такой же。
Да,существоватьфактическая ситуация,и非如этот。 Как правило, влияние соседства становится все слабее и слабее по мере удаления от пикселя из Расположения; Следовательно, чем дальше от целевого пикселя, тем меньшим должен быть эффект, т. е. тем меньше вес.
использовать Гауссов фильтр может реализовать эту связь. Как следует из названия,Этот фильтр использует функцию Гаусса, применяя одномерную формулу, определяющую распределение весов для данной окрестности из:
Эта Воля выдает колоколообразную кривую,Чтосерединаa
дапик кривойиз高степень,b
да峰середина心иливсеценитьиз Расположение,c
дастандартный差или сигма, которая указывает ширину пика. Колоколообразная кривая. иметь参числоизколоколообразная криваяиз Примерследующее:a = 1, b = 0, c = 1
。
Для фильтрации с использованием функции Гаусса,Мы должны Воля его расширения приезжать в двумерное пространство.,не теряя общности,такой жеизконцепция Применимо Рисовать одномерную версию здесь.
сейчассуществовать,Воляx
轴Видетьдляядерныйсерединаизиндекс веса(Чтосередина 0 дасередина心масса),Воляy
轴Видетьдлямассаценить。 поэтому,еслинас移动ядерныйделать Чтосередина心(существоватьx = 0
виз曲线середина心)и Цель Пиксель重合,Тогда наибольший вес Воли (кривая из пика ценить) присваивается Целью пикселя.,а затем отойти от основного центра,Вес будет продолжать снижаться,поэтому,Присвойте меньшую важность пикселям, находящимся дальше от цели пикселей.
существуют в этом фильтре,Пиксели по соседству сортируются по их интенсивности ценить,Цель Пиксель被排序назадиз Районизсередина Кусочекчисло代替。 медианный фильтрдляшкала исключениядлясоль и перецшумизодин种шум非常有效,Как показано ниже:
для каждого типа фильтра добрый,нас Волясуществоватьприложениесерединадобавить в разных предметах меняю. изменятьприезжатьres/menu/soft_scanner.xml
документи Открытьэток Сумка含к Внизменюэлемент:
<item
android:id="@+id/img_blurr"
android:enabled="true"
android:orderInCategory="4"
android:showAsAction="ifRoom"
android:title="@string/list_blurr"
android:titleCondensed="@string/list_blurr_small"
android:visible="true">
<menu>
<item
android:id="@+id/action_average"
android:title="@string/action_average"/>
<item
android:id="@+id/action_gaussian"
android:title="@string/action_gaussian"/>
<item
android:id="@+id/action_median"
android:title="@string/action_median"/>
</menu>
</item>
OpenCV для нашего существования Это обсуждение Каждый фильтр предоставляет готовую к использованию реализацию. наснуждаться做из Сразуда指定один些идентификация В过滤устройствоиз参число,Затемнас Сразу Можеткначинать Понятно。
существоватьSoftScanner
Активностьсередина,наснуждатьсяредактироватьonOptionesItemSelected()
методидобавить в Следующие ситуации:
else if(id==R.id.action_average)
{
if(sampledImage==null)
{
Context context = getApplicationContext();
CharSequence text = "You need to load an image first!";
int duration = Toast.LENGTH_SHORT;
Toast toast = Toast.makeText(context, text, duration);
toast.show();
return true;
}
Mat blurredImage=new Mat();
Size size=new Size(7,7);
Imgproc.blur(sampledImage, blurredImage, size);
displayImage(blurredImage);
return true;
}
else if(id==R.id.action_gaussian)
{
/* code to handle the user not loading an image**/
/**/
Mat blurredImage=new Mat();
Size size=new Size(7,7);
Imgproc.GaussianBlur(sampledImage, blurredImage, size, 0,0);
displayImage(blurredImage);
return true;
}
else if(id==R.id.action_median)
{
/* code to handle the user not loading an image**/
/**/
Mat blurredImage=new Mat();
int kernelDim=7;
Imgproc.medianBlur(sampledImage,blurredImage , kernelDim);
displayImage(blurredImage);
return true;
}
для Для каждого выбранного фильтра мы выполняем один и тот же процесс:
Если пользователь не загружает картинку в картинке, мы обрабатываем следующую ситуацию:
if(sampledImage==null)
{
Context context = getApplicationContext();
CharSequence text = "You need to load an image first!";
int duration = Toast.LENGTH_SHORT;
Toast toast = Toast.makeText(context, text, duration);
toast.show();
return true;
}
длясредний фильтр,нас调использоватьImgproc.blur()
метод,И передаем следующие параметры:
входитьизображениеизMat
объект; Он может иметь любое количество каналов, которые обрабатываются независимо.
После фильтра приложения,Выходное изображениеизMat
объект。
инструктироватьхотетьиспользоватьизядерный(Сосед)размеризSize
объект。 существоватьнасиз例ребеноксередина,ядерныйизразмердля7 x 7
。
Mat blurredImage=new Mat();
Size size=new Size(7,7);
Imgproc.blur(sampledImage, blurredImage, size);
displayImage(blurredImage);
return true;
хотетьприложение Гауссов фильтр,насиспользоватьк Вниз参число调использоватьImgproc.GaussianBlur()
метод:
входитьизображениеизMat
объект。
Выходное изображениеизMat
объект。
инструктироватьядерныйразмеризSize
объект。 Вы можете использовать разную высоту и ширину сердечника. Да,Оба должны быть для нечетных и положительных чисел.
представлятьx
方Кначальствостандартный差изпара精степеньформа。 существоватьнасиз例ребеноксередина,нас Воля Чтонастраиватьдля0
,к便 OpenCV По ширине ядра мы рассчитываем цену.
представлятьy
方Кстандартный差изпара精степеньформа,настакже Воля Чтонастраиватьдля0
,к便 OpenCV Рассчитайте значение, исходя из высоты ядра:
Mat blurredImage=new Mat();
Size size=new Size(7,7);
Imgproc.GaussianBlur(sampledImage, blurredImage, size, 0,0);
displayImage(blurredImage);
return true;
наконец,хотетьиспользоватьмедианный фильтр,насиспользоватьк Вниз参число调использоватьImgproc.medianBlur()
:
входитьизображениеизMat
объект。
Выходное изображениеизMat
объект。
Целое число, обозначающее размер ядра, мы используем цену, потому что медианный фильтрда кассетного фильтра (то есть ширина сердцевины равна ее высоте). Да,Размерность ядра должна быть оценена как для положительного числа, так и для нечетного числа.
Mat blurredImage=new Mat();
int kernelDim=7;
Imgproc.medianBlur(sampledImage,blurredImage , kernelDim);
displayImage(blurredImage);
return true;
На изображении ниже показаны три варианта использования ядерных изсредний разных размеров. фильтриз Пример(левый:11
,середина心:25
иверно:35
)。 ты会смотретьприезжать,По мере увеличения размера ядра,Детали начинают выпадать:
Ниже приведен пример медиафильтрационного удаления шума соли и перца в эффекте:
Пространственная фильтрация из другого приложениясуществует изображение, чтобы найти приезжающие края (границы объектов). Процесс обнаружения границ зависит от расчета скорости изменения интенсивности пикселей. Интуитивно,Когда скорость изменений высока,существующая Эта область, скорее всего, будет содержать существующие ребра.
для Рассчитать скорость изменения,Наша концепция производных в дискретной области,потому чтодлядляразмердляn x n
изизображение,нас Только有Номер строки1, 2, ..., n
и Номер столбца1, 2, ..., n
,инас没有Номер строки1.1, 1.2, ...
。
позволятьнас考虑изображениеI(x, y)
,Чтосерединаx
да Номер столбца,y
да Номер строки。 потому чтоэтодадва个переменнаяизфункция,поэтомунас Воляв соответствии сx
из Дискретный导число逼近чиновник,Вычислите частные производные для каждой переменной независимо:
этотдаизображение Взаимнодляx
изодинуровень导число,и且для Понятновычислитьизображение Взаимнодляy
изодинуровень导число,Мы используем следующую формулу:
поэтому,дляx
Выбиратьизображениеиз导числоочень просто。 нас Выбиратьx + 1
из Пиксельценить,иотx-1
из Пиксельсерединауменьшать去это,Это называется центральной разностью,y
такжеда如этот。
наконец,потому что изображение имеет два измерения (строки и столбцы).,поэтомудляна пиксель(один个использовать Вx
方К,один个использовать Вy
方К),нас得приезжатьодин个梯степень К量[∂I/∂x; ∂I/∂y]
,И потому что это вектор,Таким образом, это может сказать нам две вещи:
Взгляд в будущее,Мы можем разработать простое ядро для расчета средней центральной разницы,кпопытаться найтиприезжатьизображениесуществоватьx
иy
方Кначальствоиз导число,Как показано ниже:
Теперь мы можем выполнить следующие шаги. Подвести итог процесс обнаружения первого производного края:
x
方Киз导число; выход Воляда被ядерный过滤дляK[x]
изизображение。y
方Киз导число; выход Воляда另один个кK[y]
ядерный过滤изизображение。На рисунке ниже показан пример,это针верно原始изображение(левый)существоватьx
方Кначальствовычислитьодинуровень导числок Обнаружение垂直край(середина心),дляy
方КПриходитьвычислить水平край(верно):
OpenCV для предлагает различные издетекторы края。 Наше устройство «Воля startuseiz» названо для Детектор границ Собеля。 Основная идея здесь — конструкция ядра свертки:
ядерный Даже加强调K[x]
изсередина心行иK[y]
изсередина心列。
Еще один очень хороший издетектор край (также известный как лучший детектор) да Детектор края Канни。
существования Детектора края Канни, мы определяем краевые пиксели следующим образом:
нас Волясуществоватьнасизприложениесерединадобавить в Некоторые пункты меню для запуска нашего Воляиспользоватьиз разных детекторов края。 изменятьприезжатьres/menu/soft_scanner.xml
документи Открытьэток Сумка含к Внизменюэлемент:
<item
android:id="@+id/img_edge_detection"
android:enabled="true"
android:orderInCategory="5"
android:showAsAction="ifRoom"
android:title="@string/list_ed"
android:titleCondensed="@string/list_ed_small"
android:visible="true">
<menu>
<item
android:id="@+id/action_sobel"
android:title="@string/action_sobel"/>
<item
android:id="@+id/action_canny"
android:title="@string/action_canny"/>
</menu>
</item>
существовать本节середина,нас Воля同часиспользовать Sobel и Детектор края Канни найти изображение в изедже. Мы начнем с Sobel Краевой фильтр начинается.
существоватьSoftScanner
Активностьсередина,наснуждатьсяредактироватьonOptionesItemSelected()
методидобавить в Следующие ситуации:
else if(id==R.id.action_sobel)
{
if(sampledImage==null)
{
Context context = getApplicationContext();
CharSequence text = "You need to load an image first!";
int duration = Toast.LENGTH_SHORT;
Toast toast = Toast.makeText(context, text, duration);
toast.show();
return true;
}
Mat blurredImage=new Mat();
Size size=new Size(7,7);
Imgproc.GaussianBlur(sampledImage, blurredImage, size, 0,0);
Mat gray = new Mat();
Imgproc.cvtColor(blurredImage, gray, Imgproc.COLOR_RGB2GRAY);
Mat xFirstDervative =new Mat(),yFirstDervative =new Mat();
int ddepth=CvType.CV_16S;
Imgproc.Sobel(gray, xFirstDervative,ddepth , 1,0);
Imgproc.Sobel(gray, yFirstDervative,ddepth , 0,1);
Mat absXD=new Mat(),absYD=new Mat();
Core.convertScaleAbs(xFirstDervative, absXD);
Core.convertScaleAbs(yFirstDervative, absYD);
Mat edgeImage=new Mat();
Core.addWeighted(absXD, 0.5, absYD, 0.5, 0, edgeImage);
displayImage(edgeImage);
return true;
}
потому что Sobel да Первый детектор производных края,поэтомунас Воляследовать Передний Обзоризпроцесс:
Сгладьте изображение с помощью одного из наших фильтров размытия «Изучить размытие».,Чтобы уменьшить шумовой отклик при расчете краевых пикселей. Сразудля нас,существовать большую часть времени,насиспользоватьразмердля7 x 7
из Гауссов фильтр:
Mat blurredImage=new Mat();
Size size=new Size(7,7);
Imgproc.GaussianBlur(sampledImage, blurredImage, size, 0,0);
Волягладкийизображение Конвертировать для灰степеньизображение:
Mat gray = new Mat();
Imgproc.cvtColor(blurredImage, gray, Imgproc.COLOR_RGB2GRAY);
использоватьImgproc.Sobel()
и传入к Вниз参число,вычислить灰степеньизображениеизx
иy
одинуровень导число:
делатьдляисточникизображениеизMat
объект。
делатьдля Выходное изображениеизMat
объект。
целочисленная глубина,использовать Винструктировать Выходное изображениеиз深степень。 Большую часть времени существуют, вход и выход, а также глубина зображения одинакова. Да,Когда мы существуем, вычисляем производную в некоторых случаях,Долженценитьдлягруз(Прямо сейчас,от Белый(255
)Изменятьдлячерный(0
,derivative = -255 - 0 = -255
)。 поэтому,еслинасиспользоватьизMat
объектиз深степеньдлябез подписи 8 бит (серые изображения сохраняют только 0 приезжать 255 между ценить),ногруз导числоизценить Воля溢出инастраиватьдля0
,Прямо сейчас错过этот个边。 Чтобы обойти эту проблему, мы используем символ из 16 Выходное изображение разрядности для хранения отрицательных производных.
насхотетьвычислитьизx
уровеньиз整число。 нас Воля Чтонастраиватьдля1
квычислитьx
изодинуровень导число。
насхотетьвычислитьизy
уровеньиз整число。 нас Воля Чтонастраиватьдля1
квычислитьy
изодинуровень导число。
Уведомление,хотетьвычислитьx
方Кначальствоиз梯степень,насиспользоватьx-order = 1
иy-order = 0
。 насверноy
方Кдобрыйсделай это как。
Вот код:
Mat xFirstDervative =new Mat(),yFirstDervative =new Mat();
int ddepth=CvType.CV_16S;
Imgproc.Sobel(gray, xFirstDervative,ddepth , 1,0);
Imgproc.Sobel(gray, yFirstDervative,ddepth , 0,1);
нас调использоватьCore.convertScaleAbs()
существоватьвходитьMat
объектначальство依次执行三个操делать:
Mat
объектизценить; Да,потому что мы не передаем какой-либо коэффициент масштабирования,поэтому跳过Понятно Увеличитьшаг。Mat
объектсерединакаждый元素изабсолютныйценить。 наснуждатьсяэтотшаг,потому чтодлянасхранилище Понятноx
иy
одинуровень导числоизгрузценить,Но на самом деле нас волнует производная изабсолютная цена.,и且нас希望能够Воля Этиценитьхранилищесуществоватьбез подписииз 8 КусочекMat
объектсередина(хранилищеот 0 приезжать 255 изценить)。Mat
объект。Core.convertScaleAbs()
из参числодавходитьивыходMat
объект:
Mat absXD=new Mat(),absYD=new Mat();
Core.convertScaleAbs(xFirstDervative, absXD);
Core.convertScaleAbs(yFirstDervative, absYD);
нас尝试использоватьCore.addWeighted()
Приходить估计梯степеньразмерк显示крайизображение,Долженфункциявычислитьдва个изображениеиз Взвешенныйи。 Мы передаем следующие параметры для достижения:
Mat
объект。 нассуществоватьx
方Кпередача Понятноабсолютныйодинуровень导число。0.5
。Mat
объект。 нас沿y
方Кпередача Понятноабсолютныйодинуровень导число。0
。Mat
объект,использовать Вхранилище Выходное изображение。Это количество градиентов примерно стоит. Сразу для этого примера это дахорошийиз. Да,Если вам нужно рассчитать фактическую величину градиента,тогда должениспользоватьэтотчиновникgradient magnitude = √(f[x]² + f[y]²)
,Чтосерединаf[x], f[y]
соответственнодаx
иy
方Кначальствоизодинуровень导числоизценить。
Вот код:
Mat edgeImage=new Mat();
Core.addWeighted(absXD, 0.5, absYD, 0.5, 0, edgeImage);
наконец,нас显示edgeImage
:
displayImage(edgeImage);
Приложение Пример обнаружения границ фильтра Собеля
приложение Детектор края Канни проще; На самом деле нам просто нужно существовать OpenCV выполнить функцию, Детектор края Каннииз Все этапы выполняем мы. проходитьэтот种抽象水平,нас Тольконуждаться指定один些алгоритм参число Прямо сейчас Может。
существоватьSoftScanner
Активностьсередина,наснуждатьсяредактироватьonOptionesItemSelected()
методидобавить в Следующие ситуации:
else if(id==R.id.action_canny)
{
if(sampledImage==null)
{
Context context = getApplicationContext();
CharSequence text = "You need to load an image first!";
int duration = Toast.LENGTH_SHORT;
Toast toast = Toast.makeText(context, text, duration);
toast.show();
return true;
}
Mat gray = new Mat();
Imgproc.cvtColor(sampledImage, gray, Imgproc.COLOR_RGB2GRAY);
Mat edgeImage=new Mat();
Imgproc.Canny(gray, edgeImage, 100, 200);
displayImage(edgeImage);
return true;
}
Чтобы облегчить задачу, вы можете посмотреть, как добраться:
нас Волявходитьизображение Конвертировать для灰степень,потому чтодля Canny только Применимо к灰степеньизображение:
Mat gray = new Mat();
Imgproc.cvtColor(sampledImage, gray, Imgproc.COLOR_RGB2GRAY);
нас调использоватьImgproc.Canny()
и передайте следующие параметры:
Mat
объектMat
объектCanny предположение Воляначальство限порогценитьи Вниз限порогценитьизсоотношениенастраиватьдля 2:1 приезжать 3:1。
Вот код:
Mat edgeImage=new Mat();
Imgproc.Canny(gray, edgeImage, 100, 200);
наконец,нас显示edgeImage
:
displayImage(edgeImage);
приложение Детектор края Каннииз Пример
поэтому,насуже经смотретьприезжать如何Обнаружениекрай; Но да, этот процесс происходит попиксельно, отвечает на вопрос «нет для пикселя». В дальнейшем нам понадобится не тестирование кромок для анализа формы, а более конкретная информация. Нам, Воля, нужно больше представителей.
Например,Если у нас есть коробка с картинкой,и выполнил обнаружение краев,В конечном итоге мы получаем десятки тысяч краевых пикселей. Но да,Если мы попытаемся подогнать линию к этим краевым пикселям,тогда ты сможешь приехать в другую сторону,Это более символичная и практичная форма представления.
Есть много способов провести линию через несколько точек.,и且Преобразование Хафапризнанныйдлядаодин种约束不足изметод,где мы толькоиспользуем точку, чтобы найти все линии, которые могут провести эту точку из,Мы используем еще одну точку, чтобы найти все, что вы можете извлечь из линии.,И продолжаем делать это по всем пунктам.
В итоге у нас появилась система голосования,где каждая точка голосуется за линию,И чем больше точек на одной линии,верно Должен行изголосование Сразу Чем выше。 Короче говоря, Преобразование Хафа Можеткописыватьдля Воляx, y
нулевой间серединаиз Отображение точекприезжатьформа интересаиз参числонулевой间。
利использоватьx
иy
нулевой间серединаиз Уравнение прямойy = ax + b
,Воля Что Изменять换длясклон(a)
изнулевой间и截Выбиратьнулевой间(b),и учитывая это преобразование,Приходите вx
иy
нулевой间серединаизточка,На самом деле да наклон и з линия в пространстве,Чтоуравнение式дляb = -ax + y
:
существовать Внизкартинасередина,нассуществоватьx
иy
нулевой间середина有五个точка(левый)。 когда Конвертировать длясклониперехватыватьнулевой间час,Мы получаем пять элементов приезжать (справа):
сейчассуществовать,x
иy
нулевой间серединаизкаждыйточка Все Воляголосование给один个склон,И существуют наклон и перехват в пространстве,поэтомунасхотеть做из Сразудасуществовать参числонулевой间серединапопытаться найтиприезжатьмаксимумценить,Эта Сразуда нас устраивает по пунктам:
существоватьначальствоодин幅изображениеизвернокартинасередина,Вы можете проголосовать по картинке слева, чтобы найти наибольшую цену.,существоватьлевыйкартинасередина,Вы можете видеть, как максимальная цена проезда соответствует этим точкам по прямой линии, наклону и пересечению.
для вертикальной линии,Наклон до бесконечности,Это сразу же означает, что использование линии из уравнения полярных координат вместо наклона и формы пересечения более практично по причине. существуют В этом случае,насхотетьиметь дело сизуравнениедаr = x cosθ + y sinθ
,нас又有два个参числоr(ρ)
иθ
,Мы, Воля, придерживаемся тех же идей,Толькодасейчассуществоватьизнулевой间дляr
иθ
и不дасклониперехватывать。
Опять следуем системе голосования,попытаться найтиприезжатьпредставлятьнасизточкаизпрямая линияизr
иθ
максимумценить。 Да,этотодин次x
иy
нулевой间серединаизточка Волядатолько弦曲线,еслидва个или多个только弦曲线существовать同одинr
иθ
в Взаимно交,Это означает, что они принадлежат одной строке:
ты Можетксуществоватьэта страницаначальствоиспользовать Маленькийпрограмма查смотреть Преобразование Хафаиз продумать ситуацию.
существуют В OpenCV у нас есть две реализации линейного преобразования Хафа:
Мы Волядобавить водин个новыйизменюэлементкзапускать Преобразование Хафаалгоритм。 изменятьприезжатьres/menu/soft_scanner.xml
документи Открытьэток Сумка含к Внизменюэлемент:
<item android:id="@+id/action_HTL"
android:enabled="true"
android:visible="true"
android:title="@string/action_HL">
</item>
Процесс преобразования линии Хафа разделен на четыре этапа:
существоватьSoftScanner
Активностьсередина,наснуждатьсяредактироватьonOptionesItemSelected()
методидобавить в Следующие ситуации:
else if(id==R.id.action_HTL)
{
if(sampledImage==null)
{
Context context = getApplicationContext();
CharSequence text = "You need to load an image first!";
int duration = Toast.LENGTH_SHORT;
Toast toast = Toast.makeText(context, text, duration);
toast.show();
return true;
}
Mat binaryImage=new Mat();
Imgproc.cvtColor(sampledImage, binaryImage, Imgproc.COLOR_RGB2GRAY);
Imgproc.Canny(binaryImage, binaryImage, 80, 100);
Mat lines = new Mat();
int threshold = 50;
Imgproc.HoughLinesP(binaryImage, lines, 1, Math.PI/180, threshold);
Imgproc.cvtColor(binaryImage, binaryImage, Imgproc.COLOR_GRAY2RGB);
for (int i = 0; i < lines.cols(); i++)
{
double[] line = lines.get(0, i);
double xStart = line[0],
yStart = line[1],
xEnd = line[2],
yEnd = line[3];
org.opencv.core.Point lineStart = new org.opencv.core.Point(xStart, yStart);
org.opencv.core.Point lineEnd = new org.opencv.core.Point(xEnd, yEnd);
Core.line(binaryImage, lineStart, lineEnd, new Scalar(0,0,255), 3);
}
displayImage(binaryImage);
return true;
}
Долженкодна самом очень просто,к Внизшагиспользовать ВОбнаружение и рисование линий:
Сначала мы обрабатываем ситуацию, когда пользователь щелкает элемент меню, но изображение не загружается:
if(sampledImage==null)
{
Context context = getApplicationContext();
CharSequence text = "You need to load an image first!";
int duration = Toast.LENGTH_SHORT;
Toast toast = Toast.makeText(context, text, duration);
toast.show();
return true;
}
Затем,насинициализацияодин个новыйизMat
объект,и Волянагрузкаизизображениеот Полныйцветтрансформация пространствадля灰степеньнулевой间。 наконец,нас调использоватьImgproc.Canny()
Воля灰степеньизображение Конвертировать длятолько显示крайиздвоичныйизображение:
Mat binaryImage=new Mat();
Imgproc.cvtColor(sampledImage, binaryImage, Imgproc.COLOR_RGB2GRAY);
Imgproc.Canny(binaryImage, binaryImage, 80, 100);
Внизодин步да调использоватьImgproc.HoughLinesP()
,Это оригинальный метод Преобразования Хафа из вероятностной версии.,И передаем следующие параметры:
Mat
объект,Представляет загрузку изображенияиз двоичного изображения ВерсияMat
объект,использовать ВВоля Обнаружениеприезжатьиз Линия зарезервированадля参числоx_start, y_start, x_end, y_end
ρ
източка辨率(к Пиксельдляодин Кусочек)из倍число; существуют мы из примера, мы воля его настройкидля одного пикселяθ
из弧степеньточка辨率изпара精степень; существуем из-за случая,нас Воля Чтонастраиватьдля 1 степень(pi / 180)
в целом,когдаиспользовать Преобразование При использовании вероятностной версии вы можете захотеть использовать меньший порог для оценки алгоритма. небольшие изменения при голосовании из баллов. Да,существоватьстандартныйиз Преобразование Хафасередина,отвечатьиспользовать Даже大изпорогценить。
Вот код:
Mat lines = new Mat();
int threshold = 50;
Imgproc.HoughLinesP(binaryImage, lines, 1, Math.PI/180, threshold);
Наконец, мы будем бинарное и зображение Конвертировать для полного и цветового пространствок显示Обнаружениеприезжатьизлиния,Затем существование обнаруживает строки прибытия в цикле и использует параметры, рисуя их одну за другой.,x_start, y_start, x_end, y_end
:
Imgproc.cvtColor(binaryImage, binaryImage, Imgproc.COLOR_GRAY2RGB);
for (int i = 0; i < lines.cols(); i++)
{
double[] line = lines.get(0, i);
double xStart = line[0],
yStart = line[1],
xEnd = line[2],
yEnd = line[3];
org.opencv.core.Point lineStart = new org.opencv.core.Point(xStart, yStart);
org.opencv.core.Point lineEnd = new org.opencv.core.Point(xEnd, yEnd);
Core.line(binaryImage, lineStart, lineEnd, new Scalar(0,0,255), 3);
}
displayImage(binaryImage);
Вы можете обнаружить линии Хафа «прибытие» в следующем входном изображении в сетке «существующие»:
по краю изображения обнаружить местонахождение толстая линия (синяя)
OpenCV для предоставляет еще одну реализацию Преобразования Хафаиз.,Но на этот раз,У нас нет тестовой линии,идав соответствии с Воляx, y
трансформация пространствадля参числонулевой间изтакой же思想Приходить Обнаружениекруглый。
длякруглыйизуравнениеr² = (x - a)² + (y - b)²
,нас Есть три参числоr, a, b
,Чтосерединаa
иb
соответственнодакруглыйсуществоватьx
иy
方Кначальствоизсередина心 ,r
дарадиус。
сейчассуществовать,Пространство параметров трехмерное из,Каждая краевая точка круга имеет право голоса в этом трехмерном пространстве.,Затемнассуществовать参числонулевой间середина搜索максимумценитьк Обнаружениекруглыйизсередина心ирадиус。
Этот процесс очень,Занимает много памяти и вычислений,и且三维нулевой间Воляочень редкий。 Хорошей новостью является то, что OpenCV использоватьсказатьдляГрадиентный метод Хафаизметод实сейчас Понятнокруглый形Преобразование Хафа。
Градиентный метод Как работает Хафаиз следующее: Для первого шага мы приложениедетектор край, например Детектор края Канни。 существуют На втором этапе мы увеличиваем аккумуляторную единицу (в двумерном пространстве) вдоль направления градиента для каждого краевого пикселя. Интуитивно понятно, что если мы сталкиваемся с кругом, то накопительная единица с более высоким правом голоса фактически является центром круга. Теперь, когда мы создали список потенциальных центров, нам нужно найти радиус круга. поэтому,для каждого центра,Мы учитываем краевые пиксели, сортируя их по центру их расположения и расстоянию.,И сохраняйте максимальное количество краевых пикселей (голосования) за пределами одного радиуса:
для триггерного раунда Преобразование Хафа,нас Воляодин个менюэлементдобавить Вприжатьез теперь доступен в меню середина. изменятьприезжатьres/menu/soft_scanner.xml
документи Открытьэток Сумка含к Внизменюэлемент:
<item android:id="@+id/action_CHT"
android:enabled="true"
android:visible="true"
android:title="@string/action_CHT">
</item>
Процесс обнаружения круга и процесс обнаружения линии очень похожи:
насредактироватьonOptionsItemSelected()
киметь дело скруглый Преобразование Ситуация в Хафаизе:
else if(id==R.id.action_CHT)
{
if(sampledImage==null)
{
Context context = getApplicationContext();
CharSequence text = "You need to load an image first!";
int duration = Toast.LENGTH_SHORT;
Toast toast = Toast.makeText(context, text, duration);
toast.show();
return true;
}
Mat grayImage=new Mat();
Imgproc.cvtColor(sampledImage, grayImage, Imgproc.COLOR_RGB2GRAY);
double minDist=20;
int thickness=5;
double cannyHighThreshold=150;
double accumlatorThreshold=50;
Mat circles = new Mat();
Imgproc.HoughCircles(grayImage, circles, Imgproc.CV_HOUGH_GRADIENT, 1, minDist,cannyHighThreshold,accumlatorThreshold,0,0);
Imgproc.cvtColor(grayImage, grayImage, Imgproc.COLOR_GRAY2RGB);
for (int i = 0; i < circles.cols(); i++)
{
double[] circle = circles.get(0, i);
double centerX = circle[0],
centerY = circle[1],
radius = circle[2];
org.opencv.core.Point center = new org.opencv.core.Point(centerX, centerY);
Core.circle(grayImage, center, (int) radius, new Scalar(0,0,255),thickness);
}
displayImage(grayImage);
return true;
}
Код круга Преобразование Хафаиз тот же,Используйте тестовую линию В,За исключением следующих частей:
double minDist=20;
int thickness=5;
double cannyHighThreshold=150;
double accumlatorThreshold=50;
Mat circles = new Mat();
Imgproc.HoughCircles(grayImage, circles, Imgproc.CV_HOUGH_GRADIENT, 1, minDist,cannyHighThreshold,accumlatorThreshold,0,0);
Imgproc.cvtColor(grayImage, grayImage, Imgproc.COLOR_GRAY2RGB);
for (int i = 0; i < circles.cols(); i++)
{
double[] circle = circles.get(0, i);
double centerX = circle[0],
centerY = circle[1],
radius = circle[2];
org.opencv.core.Point center = new org.opencv.core.Point(centerX, centerY);
Core.circle(grayImage, center, (int) radius, new Scalar(0,0,255),thickness);
}
наспроходить调использоватьImgproc.HoughCircles()
и Воляк Вниз参числопередача给это Приходить Обнаружениекруглый:
Mat
объект,выражать 8 Кусочек одноканальный ввод в оттенках серого и зображение.Mat
объект,Волядержать Обнаружениеприезжатьизкруглый。 матрицаиз每один列Воля Сумка含один个Зависит от Эти参числоx, y, r
выражатьизкруглый。1
,но累加устройство Воляиметьивходитьизображениетакой жеизразмер(宽степеньи高степень)。 еслинаспроходить3
,Тогда размер аккумулятора Воля для входного изображения будет равен одной трети.0
。0
。наконец,насцикл ОбнаружениеприезжатьизкруглыйииспользоватьCore.circle()
逐одинрисовать。
существовать本главасередина,Мы ввели понятие пространственной фильтрации,и展示Понятноот Снижение шумаприезжатьобнаружение краясуществовать Ядро сверткисерединаиздругойприложение。 Мы видели, как приезжатьиспользовать OpenCV проходитьсредний,Гауссовский фильтр Приходитьгладкийизображение。 Мы также будем OpenCV Реализовано для Sobel и Детектор края Канни。 В дополнение к обнаружению сглаживания краев изображений, мы также представляем метод под названием «Преобразование». Хафаиз знаменитая технология анализа формы, так что линии и круги соответствуют граничным пикселям.
существовать Внизодинглавасередина,Мы, Воля, продолжаем развивать приложение,Итак, используйте эти концепции для обнаружения краев и подгонки линий, чтобы найти подходящее место для преобразования и сделать некоторую перспективу. Коррекция,от И сделайте так, чтобы наше устройство для захвата документов с камеры выглядело так, как будто оно было отсканировано.
В этой главе мы продолжаем существование главы № 3 «Приложение 2: Программный сканер» в запуске приложения для основ.
Мы Воляиспользоватьуже经讨论过изконцепция(Прямо сейчасобнаружение краяи霍夫线Изменять换)в четырехугольникобъектруководить透Видеть Коррекция。 Воляперспективная трансформация приложения Вобъект Воля меняет то, как мы видим объектиз. когдатыдлядокумент,При фотографировании чеков и т. д.,Если вы хотите лучше просмотреть снимок, изображениеилидобрый, как отсканированное из копии,Эта идея Воле пригодилась.
Мы, Воля, рассмотрим, как приехать исполь зовать три разных способа реализации этой идеи:
Изображения могут претерпевать ряд преобразований. 最简одинизсписоксуществоватьздесь。
по сути,существоватьизображениекоординировать Кастрюлясередина,насхотеть做изда Воляна пиксель移Кусочекp = [x, y]
,Что量дляt = [t[x], t[y]]
。 Например,нас Можетк Воля Пиксельp
изизменять换写дляp' = p + t
。
существующее Это конвертируется,Мы, Воля, вращаем приложение В каждый пиксель,Затем отправляйтесь в Кастрюлю. потому что сохраняется евклидово расстояние,поэтому Должен Изменять换такжесказатьдля二维欧几里得Изменять换。
нас Можетк Воляэтот Изменять换写дляp' = Rp + t
,ЧтосерединаR
да2×2
матрица,ждать ВR = [cosθ, -sinθ; sinθ, cosθ]
,θ
да旋изменятьрогстепень。
Это также называется для,для преобразования подобия,существовать Это меняется,Мы добавилимасштабный коэффициентs
,к便Можетк Воля Изменять换выражатьдляp' = sRp + t
。 Это преобразование Воля сохраняет углы между линиями.
существовать Аффинныйизменять换середина,параллельные линии остаются параллельными,и且Можетквыражатьдляp' = Ap*
,Чтосерединаp* = [x, y, 1]
иA = [a, b, c; d, e, f]
。
Это также называется для,для проективной трансформации,существовать Это меняется,насиспользовать3×3
матрицаи不да2×3
матрица Приходить Даже改Пиксельиз Видетьточка。 Аффинное преобразование иперспективное Основное отличие трансформации от изда состоит в том, что последняя не сохраняет параллельных линий, а сохраняет только свою линейность.
может кто-нибудь сказать,透Видеть В основной идее без коррекции изменить матрицу одноракурсного преобразования,Эту матрицу можно использовать для лучшего наблюдения за интересующими объектами.
Чтобы найти приезжатьэту матрицу,Сначала нам нужно использовать нашу идею существования для обнаружения интересующих объектов, обсуждаемых в главе 3 главы «Приложение 2: Программный сканер».,выборгруппа достопримечательностей,Тогда укажите эти достопримечательности из Расположение,Для лучшего обзора объекта.
Этот набор пунктов является примером возможного угла даобъекта, если мы ищем перспективную перспективу. преобразуя матрицу в Волю, эти угловые из координат меняются для экрана устройства из угловых, соответствующих из, мы Воля получаем доброе-подобное сканирование из вида 。
Согласно предыдущему примеру,Мы с Волей обсуждаем три пути перспективы Коррекцияиз,И продемонстрируем разные способы найти этих персонажей, приезжать.,к Учреждатьнеобходимыйизверноотвечать关系кпопытаться найтиприезжатьподходящийизперспективная матрица преобразования.
Мы проводим первый эксперимент перспективной Коррекцияиз Воляда неизменно из пробы. Мы выполним следующие шаги:
Мы добавим дополнительные пункты меню, чтобы начать процесс коррекции перспективы. изменятьприезжатьres/menu/soft_scanner.xml
документи Открытьэток Сумка含к Внизменюэлемент:
<item
android:id="@+id/action_rigidscan"
android:enabled="true"
android:orderInCategory="6"
android:title="@string/action_rigidscan"
android:visible="true">
</item>
существовать Активностьсередина,наснуждатьсяредактироватьonOptionesItemSelected()
методипроходитьвыбирать刚性扫描选элемент Приходитьдобавить в случае для работы с пользователями.
Первый шаг — убедиться, что пользователь загрузил изображение:
else if(id==R.id.action_rigidscan)
{
if(sampledImage==null)
{
Context context = getApplicationContext();
CharSequence text = "You need to load an image first!";
int duration = Toast.LENGTH_SHORT;
Toast toast = Toast.makeText(context, text, duration);
toast.show();
return true;
}
Волявходитьизображение Конвертировать для灰степеньизображение:
Mat gray = new Mat();
Imgproc.cvtColor(sampledImage, gray, Imgproc.COLOR_RGB2GRAY);
использовать Детектор края Каннистроитькрайизображение:
Mat edgeImage=new Mat();
Imgproc.Canny(gray, edgeImage, 100, 200);
строитькрайизображениеназад,Нам нужны линии обнаружения,поэтомунасиспользовать Вероятностное преобразование линии Хафа:
Mat lines = new Mat();
int threshold = 50;
Imgproc.HoughLinesP(edgeImage, lines, 1, Math.PI/180, threshold,60,10);
Объявите и инициализируйте необходимые переменные.,Найти приезжающих, заинтересованных в изобъектиз до четырех пограничных линий,и отбросить все строки о самом существующем объекте, обнаруженном приездеиз,Для более точной оценки границы:
boolean [] include=new boolean[lines.cols()];
double maxTop=edgeImage.rows();
double maxBottom=0;
double maxRight=0;
double maxLeft=edgeImage.cols();
int leftLine=0;
int rightLine=0;
int topLine=0;
int bottomLine=0;
ArrayList<org.opencv.core.Point> points=new ArrayList<org.opencv.core.Point>();
существовать Вниз面изfor
циклсередина,Мы проверяем каждую строку, чтобы найти крайнюю левую границу интересующего объекта. После поиска места,нас Воля Чтоверноотвечатьизinclude
число组元素настраиватьдляtrue
,Чтобы избежать поиска других пограничных линий, выберите ту же строку еще раз:
for (int i = 0; i < lines.cols(); i++)
{
double[] line = lines.get(0, i);
double xStart = line[0], xEnd = line[2];
if(xStart<maxLeft && !include[i])
{
maxLeft=xStart;
leftLine=i;
}
if(xEnd<maxLeft && !include[i])
{
maxLeft=xEnd;
leftLine=i;
}
}
include[leftLine]=true;
Найдя линию приезжать, мы с Воля ее две точки добавляем. вприезжатьpoints
число组списоксередина. Позже, когда мы оценим границу границы, Воляиспользовать этот список массивов:
double[] line = lines.get(0, leftLine);
double xStartleftLine = line[0],
yStartleftLine = line[1],
xEndleftLine = line[2],
yEndleftLine = line[3];
org.opencv.core.Point lineStartleftLine = new org.opencv.core.Point(xStartleftLine, yStartleftLine);
org.opencv.core.Point lineEndleftLine = new org.opencv.core.Point(xEndleftLine, yEndleftLine);
points.add(lineStartleftLine);
points.add(lineEndleftLine);
Ту же операцию проделаем, чтобы найти крайнюю правую границу проживания:
for (int i = 0; i < lines.cols(); i++)
{
line = lines.get(0, i);
double xStart = line[0], xEnd = line[2];
if(xStart>maxRight && !include[i])
{
maxRight=xStart;
rightLine=i;
}
if(xEnd>maxRight && !include[i])
{
maxRight=xEnd;
rightLine=i;
}
}
include[rightLine]=true;
Воля принадлежит к крайней правой граничной линии из точки добавить вприезжатьpoints
число组списоксередина:
line = lines.get(0, rightLine);
double xStartRightLine = line[0],
yStartRightLine = line[1],
xEndRightLine = line[2],
yEndRightLine = line[3];
org.opencv.core.Point lineStartRightLine = new org.opencv.core.Point(xStartRightLine, yStartRightLine);
org.opencv.core.Point lineEndRightLine = new org.opencv.core.Point(xEndRightLine, yEndRightLine);
points.add(lineStartRightLine);
points.add(lineEndRightLine);
Найдите верхнюю границу проживания:
```java
for (int i = 0; i < lines.cols(); i++)
{
line = lines.get(0, i);
double yStart = line[1],yEnd = line[3];
if(yStart<maxTop && !include[i])
{
maxTop=yStart;
topLine=i;
}
if(yEnd<maxTop && !include[i])
{
maxTop=yEnd;
topLine=i;
}
}
include[topLine]=true;
```
points
число组списоксередина:```java
line = lines.get(0, topLine);
double xStartTopLine = line[0],
yStartTopLine = line[1],
xEndTopLine = line[2],
yEndTopLine = line[3];
org.opencv.core.Point lineStartTopLine = new org.opencv.core.Point(xStartTopLine, yStartTopLine);
org.opencv.core.Point lineEndTopLine = new org.opencv.core.Point(xEndTopLine, yEndTopLine);
points.add(lineStartTopLine);
points.add(lineEndTopLine);
```
```java
for (int i = 0; i < lines.cols(); i++)
{
line = lines.get(0, i);
double yStart = line[1],yEnd = line[3];
if(yStart>maxBottom && !include[i])
{
maxBottom=yStart;
bottomLine=i;
}
if(yEnd>maxBottom && !include[i])
{
maxBottom=yEnd;
bottomLine=i;
}
}
include[bottomLine]=true;
```
points
число组списоксередина:```java
line = lines.get(0, bottomLine);
double xStartBottomLine = line[0],
yStartBottomLine = line[1],
xEndBottomLine = line[2],
yEndBottomLine = line[3];
org.opencv.core.Point lineStartBottomLine = new org.opencv.core.Point(xStartBottomLine, yStartBottomLine);
org.opencv.core.Point lineEndBottomLine = new org.opencv.core.Point(xEndBottomLine, yEndBottomLine);
points.add(lineStartBottomLine);
points.add(lineEndBottomLine);
```
MatOfPoint2f
объект:```java
MatOfPoint2f mat=new MatOfPoint2f();
mat.fromList(points);
```
Imgproc.minAreaRect()
и传入насранееинициализацияизточкаизматрица Приходитьпопытаться найтиприезжатьграницапрямоугольник。 Функция пытается найти жилье, которое соответствует набору точек и имеет все возможные суммы. маленькийобластьизпрямоугольник。 Когда нас интересует точка на линии границы, мы получаем линию границы:```java
RotatedRect rect= Imgproc.minAreaRect(mat);
```
```java
org.opencv.core.Point rect_points[]=new org.opencv.core.Point [4];
rect.points(rect_points);
```
```java
Mat correctedImage=new Mat(sampledImage.rows(),sampledImage.cols(),sampledImage.type());
```
Mat
объект,Интересующийся Вхранилищеобъектиз Четыреуголь,Еще один соответствующий ракурс с Вхранилищеизображениеиз.,существует перспектива Коррекция нашего Волясуществовать, который проявляет интерес изобъектов:```java
Mat srcPoints=Converters.vector_Point2f_to_Mat(Arrays.asList(rect_points));
Mat destPoints=Converters.vector_Point2f_to_Mat(Arrays.asList(new org.opencv.core.Point[]{
new org.opencv.core.Point(0, correctedImage.rows()),
new org.opencv.core.Point(0, 0),
new org.opencv.core.Point(correctedImage.cols(),0),
new org.opencv.core.Point(correctedImage.cols(), correctedImage.rows())
}));
```
Imgproc.getPerspectiveTransform()
и Воля Чтопередачаприезжатьисточники Цельрогточка Приходитьвычислитьнеобходимыйизизменять换матрица:```java
Mat transformation=Imgproc.getPerspectiveTransform(srcPoints, destPoints);
```
наконец,насприложениепроходитьImgproc.warpPerspective()
методипередачак Вниз参числовычислить出из Изменять换:
* источникизображениеизMat
объект; В данном случае сразуда содержит объект изображения изображения.
* Выходное изображениеизMat
объект
* насхотетьприложениеизизменять换изMat
объект
* один个Size
объект,использовать Вдержать Выходное изображениеизразмер
Imgproc.warpPerspective(sampledImage, correctedImage, transformation, correctedImage.size());
наконецодин步дасуществующееприложение целесообразно после переоборудования显示насзаинтересованныйизобъект:
```java
displayImage(correctedImage);
```
![Estimating the perspective transformation using the object bounding box](https://img-blog.csdnimg.cn/img_convert/630e6742b0ebbc8e52967ff35e32a70d.png)
До (слева) и после (справа) конвертации
сейчассуществовать,Мы выполнили жесткую Коррекцию,нас希望получать Дажехорошийизрезультат。 Как упоминалось ранее, используйте перспективную коррекцию основных причин поиска объекта с четырьмя углами угла. существовать“Жесткая коррекция В разделе «виды» мы используем оценку из границы, чтобы найти интересующий угол объекта; Да,Как вы знаете,пропорциональноиз Каждая противоположная сторона параллельна,Это может снизить перспективные результаты Коррекцияиз.,потому чтодлясейчас实世界серединаизпараллельные линиисуществовать投影час必须существоватьсказатьдлякартина片平面източка сходаиз地方Взаимно交。
поэтому,использовать параллельные линии, оценивать угловые точки, не отвлекаясь от лучшего выбора,Мы можем реализовать проектную линию «Воля» (от Преобразование Хафа найти место проживанияиз проекционных линий) сохранить существование на картинке и использовать простые геометрические фигуры найти место пересечения между ними, чтобы добиться большего,Для того, чтобы найти жилье Четыреуголь.
Мы Воля выполняем из шагов следующие:
Мы Воляиспользоватьодин个меню Приходитьзапускать Гибкая коррекция перспективыпроцесс。 изменятьприезжатьres/menu/soft_scanner.xml
документи Открытьэток Сумка含к Внизменюэлемент:
<item
android:id="@+id/action_flexscan"
android:enabled="true"
android:orderInCategory="7"
android:title="@string/action_flexscan"
android:visible="true">
</item>
существоватьSoftScanner
Активностьсередина,наснуждатьсяредактироватьonOptionesItemSelected()
методидля Гибкое сканированиедобавить вновыйиз прописных и строчных букв:
Первый шаг — убедиться, что пользователь загрузил изображение:
else if(id==R.id.action_flexscan)
{
if(sampledImage==null)
{
Context context = getApplicationContext();
CharSequence text = "You need to load an image first!";
int duration = Toast.LENGTH_SHORT;
Toast toast = Toast.makeText(context, text, duration);
toast.show();
return true;
}
Выполняем те же действия, чтобы получить края:
Mat gray = new Mat();
Imgproc.cvtColor(sampledImage, gray, Imgproc.COLOR_RGB2GRAY);
Imgproc.GaussianBlur(gray, gray, new Size(7,7), 0);
Mat edgeImage=new Mat();
Imgproc.Canny(gray, edgeImage, 100, 300);
Mat lines = new Mat();
int threshold = 100;
Imgproc.HoughLinesP(edgeImage, lines, 1, Math.PI/180, threshold,60,10);
насиспользоватьчиновник:
и:
ArrayList<org.opencv.core.Point> corners=new ArrayList<org.opencv.core.Point>();
for (int i = 0; i < lines.cols(); i++)
{
for (int j = i+1; j < lines.cols(); j++)
{
org.opencv.core.Point intersectionPoint = getLinesIntersection(lines.get(0, i), lines.get(0, j));
if(intersectionPoint!=null)
{
corners.add(intersectionPoint);
}
}
}
Сейчас существуют у нас пересечение,Нам нужно найти приезжающий другой полигон, чтобы обнаружить приезжающий полигон с такой же структурой, но с меньшим количеством вершин. для этого,насиспользоватьImgproc.approxPolyDP()
метод,А Воля ему передаются следующие параметры:
один个Mat
объект,С помощью Вхранилище мы находим угловые списки для проживания.
один个Mat
объект,Это Воляхранилище аппроксимирует многоугольник из новых вершин.
Представляет максимальное расстояние между исходным многоугольником и приблизительным многоугольником и числом точности. существуют В этом случае,насиспользоватьImgproc.arcLength()
методвычислить原始多边形изпериметр,Затем Воля Что乘кодин个Маленькийпотому чторебенок0.02
,Затемиспользоватьрезультатнастраиватьдва个形状междуизмаксимумрасстояние。
логическое значение цены,Указывает, закрыта ли форма да.,существуем из примерадля:
MatOfPoint2f cornersMat=new MatOfPoint2f();
cornersMat.fromList(corners);
MatOfPoint2f approxConrers=new MatOfPoint2f();
Imgproc.approxPolyDP(cornersMat, approxConrers, Imgproc.arcLength(cornersMat, true)*0.02, true);
существуют. На этом этапе мы просто проверяем, что приближение из многоугольника k имеет как минимум четыре угла:
if(approxConrers.rows()<4)
{
Context context = getApplicationContext();
CharSequence text = "Couldn't detect an object with four corners!";
int duration = Toast.LENGTH_LONG;
Toast toast = Toast.makeText(context, text, duration);
toast.show();
return true;
}
Наш примерный список углов воля копироватьприезжать,Затем используйте этот список, чтобы найти центроид многоугольника.,Воляиспользовать Вверно近похожийрогточкаруководить排序。 Хорошо из аппроксимации центроида ценить все угловые точки аппроксимации из средней цены.
corners.clear();
Converters.Mat_to_vector_Point2f(approxConrers,corners);
org.opencv.core.Point centroid=new org.opencv.core.Point(0,0);
for(org.opencv.core.Point point:corners)
{
centroid.x+=point.x;
centroid.y+=point.y;
}
centroid.x/=corners.size();
centroid.y/=corners.size();
Теперь существуют, начинаем сортировать угловые точки по центроиду многоугольника. Сначала мы разбили их на два списка, в одном списке будут храниться Y Координаты малых В центра масс и з угла при вершине, второй список Воля Y Координаты: В центр масс и з базовый угол. Затем на основе списка в верхнем углу мы X Отсортируйте координаты сверху слева и сверху справа, и то же самое проделайте с нижним списком:
ArrayList<org.opencv.core.Point> top=new ArrayList<org.opencv.core.Point>();
ArrayList<org.opencv.core.Point> bottom=new ArrayList<org.opencv.core.Point>();
for (int i = 0; i < corners.size(); i++)
{
if (corners.get(i).y < center.y)
top.add(corners.get(i));
else
bottom.add(corners.get(i));
}
org.opencv.core.Point topLeft = top.get(0).x > top.get(1).x ? top.get(1) : top.get(0);
org.opencv.core.Point topRight = top.get(0).x > top.get(1).x ? top.get(0) : top.get(1);
org.opencv.core.Point bottomLeft = bottom.get(0).x > bottom.get(1).x ? bottom.get(1) :bottom.get(0);
org.opencv.core.Point bottomRight = bottom.get(0).x > bottom.get(1).x ? bottom.get(0) : bottom.get(1);
corners.clear();
corners.add(topLeft);
corners.add(topRight);
corners.add(bottomRight);
corners.add(bottomLeft);
Затем,картинасуществовать“Жесткая коррекция Как сделано в разделе «перспективы», устанавливаем соответствие между отсортированными ракурсами и изображением из:
Mat correctedImage=new Mat(sampledImage.rows(),sampledImage.cols(),sampledImage.type());
Mat srcPoints=Converters.vector_Point2f_to_Mat(corners);
Mat destPoints=Converters.vector_Point2f_to_Mat(Arrays.asList(new org.opencv.core.Point[]{
new org.opencv.core.Point(0, 0),
new org.opencv.core.Point(correctedImage.cols(), 0),
new org.opencv.core.Point(correctedImage.cols(),correctedImage.rows()),new org.opencv.core.Point(0,correctedImage.rows())}));
наспроходить调использоватьImgproc.getPerspectiveTransform()
и Воля Чтопередачаприезжатьисточники Цельрогточка Приходитьвычислитьнеобходимыйиз Изменять换матрица:
Mat transformation=Imgproc.getPerspectiveTransform(srcPoints, destPoints);
насприложениепроходитьImgproc.warpPerspective()
методвычислить出из Изменять换:
```java
Imgproc.warpPerspective(sampledImage, correctedImage, transformation, correctedImage.size());
```
```java
displayImage(correctedImage);
```
![Applying flexible perspective correction](https://img-blog.csdnimg.cn/img_convert/63ce737ec34ff5efdb289fab8fcfc13c.png)
До (слева) и после (справа) конвертации
Мы можем включить другую панель, используя устройство с сенсорным экраном.,И используйте домохозяйства вручную, чтобы выбрать интересующий угол объекта. Если слишком много фонового шума и автоматическая перспектива Коррекция не дает желаемых результатов,Тогда этот вариант может пригодиться.
Наша Воля следует шагам и приезжатьиз, как показано в разделе «Жесткая коррекция перспектив», очень похоже:
Как только пользователь выберет четыре угла, мы добавим еще один пункт меню, чтобы запустить ручной процесс. изменятьприезжатьres/menu/soft_scanner.xml
документи Открытьэток Сумка含к Внизменюэлемент:
<item
android:id="@+id/action_manScan"
android:enabled="true"
android:orderInCategory="8"
android:title="@string/action_manscan"
android:visible="true">
</item>
Пользователь выбирает, что интересует его из углового поста,Мы в «Воля» следуем тому же процессу. Но да,Советы по отображению координат на экране вашего устройства:
существовать АктивностьonCreate()
методсередина,нас ВоляonTouch()
事件иметь дело сустройство附加приезжатьImageView
。 существовать事件иметь дело сустройствосередина,Сначала загружаем изображениеиз масштабного коэффициента с отображением В.,ВоляImageView
середина Место选рогизкоординировать投影приезжатьнагрузкаизображение。 существования. После загрузки изображения для получения правильных координат следующие шаги такие же, как и до Воляи:
final ImageView iv = (ImageView) findViewById(R.id.SSImageView);
iv.setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View view, MotionEvent event) {
int projectedX = (int)((double)event.getX() * ((double)sampledImage.width()/(double)view.getWidth()));
int projectedY = (int)((double)event.getY() * ((double)sampledImage.height()/(double)view.getHeight()));
org.opencv.core.Point corner = new org.opencv.core.Point(projectedX, projectedY);
corners.add(corner);
Core.circle(sampledImage, corner, (int) 5, new Scalar(0,0,255),2);
displayImage(sampledImage);
return false;
}
});
Нам нужно убедиться, что пользователь загружает изображение и выбирает четыре угла:
if(sampledImage==null)
{
Context context = getApplicationContext();
CharSequence text = "You need to load an image first!";
int duration = Toast.LENGTH_SHORT;
Toast toast = Toast.makeText(context, text, duration);
toast.show();
return true;
}
if(corners.size()!=4)
{
Context context = getApplicationContext();
CharSequence text = "You need to select four corners!";
int duration = Toast.LENGTH_LONG;
Toast toast = Toast.makeText(context, text, duration);
toast.show();
return true;
}
Рассчитайте центр масс объекта и расположите четыре угла соответственно:
org.opencv.core.Point centroid=new org.opencv.core.Point(0,0);
for(org.opencv.core.Point point:corners)
{
centroid.x+=point.x;
centroid.y+=point.y;
}
centroid.x/=corners.size();
centroid.y/=corners.size();
sortCorners(corners,centroid);
Затем,картинасуществовать“Жесткая коррекция перспективы”часть:
Mat correctedImage=new Mat(sampledImage.rows(),sampledImage.cols(),sampledImage.type());
Mat srcPoints=Converters.vector_Point2f_to_Mat(corners);
Mat destPoints=Converters.vector_Point2f_to_Mat(Arrays.asList(new org.opencv.core.Point[]{
new org.opencv.core.Point(0, 0),
new org.opencv.core.Point(correctedImage.cols(), 0),
new org.opencv.core.Point(correctedImage.cols(),correctedImage.rows()),
new org.opencv.core.Point(0,correctedImage.rows())}));
Как сделано в из, строим сортировку по угловым точкам изображение соответствия между угловыми точками.
наспроходить调использоватьImgproc.getPerspectiveTransform()
и Воля Чтопередачаприезжатьисточники Цельрогточка Приходитьвычислитьнеобходимыйиз Изменять换матрица:
Mat transformation=Imgproc.getPerspectiveTransform(srcPoints, destPoints);
насприложениепроходитьImgproc.warpPerspective()
методвычислить出из Изменять换:
Imgproc.warpPerspective(sampledImage, correctedImage, transformation, correctedImage.size());
наконец,Мы существуемприложение после того, как соответствующее из преобразований показывает, что нас интересует из объекта:
displayImage(correctedImage);
Мы видели, как приезжатьиспользоватьперспективная трансформация изменить вид изображения в объектиз. Мы продемонстрировали идеи о четырехугольниках и обсудили три различных способа создания перспективы.
существовать Внизодинглавасередина,нас Воля探讨другойдобрыйформаиз Особенности изображения, как их найти и почему они для них важны.
существовать本главасередина,нас Воляначинатьразвиватьновыйизприложение。 Приложениеиз Цельда Воля две Сшивка изображениясуществовать вместе, чтобы сформировать мнение; нас Воляпредставлять Особенности изображенияизконцепцияи重хотеть性,Затем Воляэто们付诸实践。
Мы можем подвести итог следующим темам:
В этом разделе мы Волячимся Особенности изображенийиз значений и почему они для них важны.
представлять себе,遇见один个людии立Прямо сейчас Обнаружениеприезжать Долженлюдииз Лицо(Глаз,鼻ребенокимногодругойлюди Лицоособенность)из Состояние。 Вопрос в том, что нам делать? Чем мы руководствуемся при обнаружении этих черт лица? Как мы их опишем? Более того, когда мы смотрим на другого человека с такими же чертами лица, мы легко можем обнаружить различия между ними. функции。 Какую метрику мы используем для измерения этого сходства?
Мы лишь следим за обнаружением, описанием и соответствующими функциональный процесс. отвычислить机изрогстепень Приходитьсмотреть,Мы надеемся, что процесс найдет возможность приезжать повторно.,Полностью отображают и точно соответствуют характеристикам.
Эти характеристики считаются хорошими из характеристик.,Измерить преимущества и недостатки характеристик.,Мы должны учитывать его надежность и инвариантность (особенно инвариантность к масштабированию и вращению; например.,насизлюди Лицоособенность(Например Глаз)不Изменять) Пропорции лица, независимо от того, большое ли лицо или маленькое, вы легко определите, где находятся глаза). в целом,для Для достижения этой надежности,Наше обнаружение воли при использовании свойств качества рассматривается вместе с описанием функций метода из свойства качества.
Например,нас Волясмотретьприезжатьодин些детектор функций,Прямо сейчасХарриси FAST,Найдите объекты в одном масштабе (один масштаб),идругойдетектор функции (например, ORB)проходитьстроитьтак называемыйиз尺степеньнулевой间,существуют Находите объекты в нескольких масштабах.
Я нашел это прекрасной возможностью,Он знакомит с основными понятиями масштабного пространства.,Прямо сейчасиспользоватьдругойиз Пропорция尺уменьшить масштабметод Приходитьстроитьизображениепирамида。 Самый простой способ удалить X и Y направление из всех других пикселей. поэтому,Например,еслиты有один个100x100
изизображение,ноот x и y серединаудалитьвседругой Пиксель Волягенерировать100x100
изизображение。 Вы продолжаете повторять этот шаг, пока программа не будет готова. небольшой приемлемый диапазон.
первыйхотеть问изда,существуют Компьютерное зрение из фона,Какие характеристикидахорошийизособенность? Для ответа на этот вопрос лучше всего возьмем в качестве примера вершину горы. Мы можем начать рассматривать этот горный массив (прямоугольный 2)граница内изособенность,Но дапроблемане может быть найдена неоднократноприезжатьилине может адекватно описать эти характеристики,поэтомуэто们Воле сложно соответствовать.
Еще один кандидат, которого стоит искать, — это Да, используйте это. Мы уже существуем 3 глава,“приложение 2-Программный Узнайте, как обнаруживать края в «сканере», чтобы вы могли легко находить «добрый тип» среди таких функций, как приезжать. Но да, существует вопрос, как их однозначно описать, потому что для того, чтобы увидеть 1.1 и 1.2, их легко спутать с одним и тем же краем. этот个问题被сказатьдля Проблема с диафрагмой,такой же,Воле сложно соответствовать.
прямоугольник 3 Шерстяная ткань? Эта фигура выглядит хорошо, потому что, если вы переместите ее в любом направлении, область под ней будет выглядеть по-другому, поэтому она будет уникальной. Исходя из этого, можно сказать, что углы должны учитывать хорошие характеристики.
Мы отвечаем на вопрос, какие характеристики являются хорошими, и приводим пример хорошей функции. Теперь нам нужно найти способ легко их обнаружить. Итак, давайте рассмотрим изображение вершины горы. Если начать со сканирования квадратного окна и зображения, то угол Воли имеет максимальное изменение интенсивности, так как переди края разные, два ортогональных направления Воли меняются, а край только по одному направлению (х или у) изменения 。
Основная идея этого углового точечного детектора да Харрис. нас试картинапопытаться найтиприезжатьодин个补丁,Если мы существуем в этом окне патча, перемещаем скан в разных направлениях,Это Волясуществовать производит максимум изменений или изменений интенсивности.
Детектор угла Харриса, инвариант вращения. Да,Он не является масштабно-инвариантным.
создаватьиметьнулевой白АктивностьPanoActivity
изновыйприложениеидобавить вот оборудование Библиотека нагрузки и зображенияиз Функция а также нагрузка OpenCV Библиотека之назад,нас Волясуществоватьменюэлементсерединадобавить в первом пункте меню, чтобы существовать, загрузить изображение и выполнить Харрисугловой детектор。 изменятьприезжатьres/menu/pano.xml
документи Открытьэток Сумка含к Внизменюэлемент:
<itemandroid:id="@+id/action_harris"
android:orderInCategory="2"
android:title="@string/action_harris">
</item>
OpenCV для Вы предоставили разные из Объекты интересаилидетектор функции, и API иметьочень простоизинтерфейс,Можетиспользовать Вдобрыйorg.opencv.features2d
。 FeatureDetector
иметь工厂метод,и присвоен идентификатор детектора, возвращает фабричный метод Воля и это ID Издетектор корреспонденции функциииз примеров.
нас ДаженовыйonOptionsItemSelected
киметь дело с Харрисменюэлемент:
if(sampledImage==null)
{
Context context = getApplicationContext();
CharSequence text = "You need to load an image first!";
int duration = Toast.LENGTH_SHORT;
Toast toast = Toast.makeText(context, text, duration);
toast.show();
return true;
}
Mat greyImage=new Mat();
MatOfKeyPoint keyPoints=new MatOfKeyPoint();
Imgproc.cvtColor(sampledImage, greyImage, Imgproc.COLOR_RGB2GRAY);
FeatureDetector detector = FeatureDetector.create(FeatureDetector.HARRIS);
detector.detect(greyImage, keyPoints);
Features2d.drawKeypoints(greyImage, keyPoints, greyImage);
displayImage(greyImage);
Действия очень просты и заключаются в следующем:
наспервый Волявходитьизображение Конвертировать для灰степеньи实例化关键точкаобъектизматрица:
Mat greyImage=new Mat();
MatOfKeyPoint keyPoints=new MatOfKeyPoint();
Imgproc.cvtColor(sampledImage, greyImage, Imgproc.COLOR_RGB2GRAY);
насиспользоватьFeatureDetector.create
工厂метод实例化насвыбиратьиздетектор функции и передайте их ID:
FeatureDetector detector = FeatureDetector.create(FeatureDetector.HARRIS);
использоватьк Вниз Заказ调использоватьdetect
метод:
detector.detect(greyImage, keyPoints);
调использоватьdetect
методк查попытаться найтииметьк Вниз参числоиз兴趣точка:
Mat
объектMatOfKeyPoint
объект,Обнаружение приездов в точки интереса с помощью Вхранилищедля появился, чтобы обнаружить местонахождение POI,нас调использоватьFeature2d.drawKeypoints()
:
Features2d.drawKeypoints(greyImage, keyPoints, greyImage);
насиспользоватьк Вниз参число调использоватьFeature2d.drawKeypoints()
:
Mat
объектMatOfKeyPoint
Mat
объектнаконец,Обнаружение дисплея. Изображение POI:
displayImage(greyImage);
существует много ситуаций,Вам нужен ответ в режиме реального времени,Например Обнаружениесотовый телефон摄картинаголоваиз Видеть频источниксерединаизособенность。 полагаться только на Java Вызов может не обеспечить требуемой производительности и, следовательно, не соответствовать установленным срокам. существования В этом случае каждая секунда превышает 20 рамка; Это сразу для того, что я думаю, это познакомит вас с этой машиной OpenCV API из Хорошая возможность. Тебе не нужен привычный C++。 Да,Изучить структуру языка Воля - это очень помогает.
Первое, что нам нужно сделать, — это добавить в проект поддержку C++.
NDKROOT
,Наведите курсор на NDK из основной папки документов.,НапримерC:\NVPACK\android-ndk-r10c
。
${NDKROOT}/ndk-build.cmd
。
jni
。
Android.mk
изсодержаниеотвечатьследующее:
```java
LOCAL_PATH := $(call my-dir)
include$(CLEAR_VARS)
# Must include the opencv.mk file, change the path accordingly include C:\NVPACK\OpenCV-2.4.8.2-Tegra-sdk\sdk\native\jni\OpenCV-tegra3.mk
# Name the library and list the cpp source files
LOCAL_MODULE := Pano
LOCAL_SRC_FILES := Pano.cpp
LOCAL_LDLIBS += -llog -ldl
include$(BUILD_SHARED_LIBRARY)
```
Application.mk
из含量отвечатьследующее:```java
APP_PLATFORM := android-9
APP_ABI := armeabi-v7a
APP_STL := gnustl_static
APP_CPPFLAGS := -frtti -fexceptions
```
cpp
документ,оно может быть пустым,И содержит только заголовок:```java
#include <jni.h>
```
### Уведомление
STL для Вы предоставляете набор готовых издобрых решений, реализующих различные структуры данных и алгоритмы.
```java
${NDKROOT}/platforms/android-9/arch-arm/usr/include
${NDKROOT}/sources/cxx-stl/gnu-libstdc++/4.6/include
${NDKROOT}/sources/cxx-stl/gnu-libstdc++/4.6/libs/ armeabi-v7a/include
C:\NVPACK\OpenCV-2.4.8.2-Tegra-sdk\sdk\native\jni\include
```
существоватьпроект Видетькартинасередина,Щелкните правой кнопкой мыши узел приложения.,ЗатемвыбиратьОткрытьмодульнастраиватьилив соответствии сF4
。
Выберите «SDK. Расположение". существовать“Android NDK Местоположение, выберите NDK Местосуществоватьиз Оглавление。 Пожалуйста Уведомление, мы Воляиспользовать экспериментально Gradle Версия плагина 2.5 построить проект; Поэтому нам нужно NDK Версия r10e:
Если вы используетеизда Android Studio 1.3.2,нонуждаться Даженовыйgradle-wrapper.properties
и Даже改точка发 URL-адрес, как показано ниже:
distributionUrl=https\://services.gradle.org/distributions/gradle-2.5-all.zip
существоватьпроектизbuild.gradle
документсередина,Обновите путь Зависимостидобрый следующим образом:
dependencies {classpath 'com.android.tools.build:gradle-experimental:0.2.0'}
существоватьпроектдокументпапкасередина,существоватьapp\src\main
Внизсоздаватьдва个документпапкаjni
иjniLibs
。
существоватьjni
документпапкасередина,Создать новый документ,и Воля ЧтоимядляPano.cpp
。
сейчассуществовать,Навигацияприжатьез<OpenCV4AndroidSDKFolder>\sdk\native\libs\
,и ВолявседокументпапкакопироватьприезжатьновыйсоздаватьизjniLibs
документпапкасередина. тыизпроект Деревоотвечать Как показано ниже:
наснуждаться Даженовыйbuild.gradle
серединаизязык, специфичный для домена(DSL),Чтобы наш модуль мог использовать и Gradle 2.5 вместе. для этого,пожалуйста Даженовыйстроитьдокументтак, чтобы оно имяо Следующее сопоставление и оставляет зависимые методы без изменений. Пожалуйста, Уведомление, вашей Воля необходимо обновить абсолютный путь, чтобы он соответствовал вашему из Установить:
applyplugin: 'com.android.model.application' model {
android {
compileSdkVersion = 23 buildToolsVersion = "23.0.1" defaultConfig.with {
applicationId = "com.app3.pano" minSdkVersion.apiLevel = 15 targetSdkVersion.apiLevel = 19 versionCode = 1 versionName = "1.0"
}
}
//Make sure to build with JDK version 7
compileOptions.with {
sourceCompatibility=JavaVersion.VERSION_1_7 targetCompatibility=JavaVersion.VERSION_1_7
}
android.ndk {
moduleName = "Pano" ldLibs += ['log']
cppFlags += "-std=c++11" cppFlags += "-fexceptions" cppFlags += "-I${file("<OpenCV4AndroidSDK_Home>/sdk/native/jni/include")}".toString()
cppFlags += "-I${file("<OpenCV4AndroidSDK_Home>/sdk/native/jni/include/opencv")}".toString()
ldLibs += ["android", "EGL", "GLESv2", "dl", "log", "z"]// , "ibopencv_core" stl = "gnustl_shared}
android.buildTypes {
release {
minifyEnabled= false proguardFiles+= file('proguard-rules.pro')
}
}
android.productFlavors {
create("arm") {
ndk.with {
abiFilters += "armeabi" File curDir = file('./')
curDir = file(curDir.absolutePath)
String libsDir = curDir.absolutePath+"\\src\\main\\jniLibs\\armeabi\\" //"-L" + ldLibs += libsDir + "libopencv_core.a" ldLibs += libsDir + "libopencv_imgproc.a" ldLibs += libsDir + "libopencv_java.so" ldLibs += libsDir + "libopencv_features2d.a"
}
}
create("armv7") {
ndk.with {
abiFilters += "armeabi-v7a" File curDir = file('./')
curDir = file(curDir.absolutePath)
String libsDir = curDir.absolutePath+"\\src\\main\\jniLibs\\armeabi-v7a\\" //"-L" + ldLibs += libsDir + "libopencv_core.a" ldLibs += libsDir + "libopencv_imgproc.a" ldLibs += libsDir + "libopencv_java.so" ldLibs += libsDir + "libopencv_features2d.a"
}
}
create("x86") {
ndk.with {
abiFilters += "x86"
}
}
create("mips") {
ndk.with {
abiFilters += "mips"
}
}
create("fat") {
}
}
}
}
наконец,наснуждатьсядля OpenCV модуль Даженовыйbuild.gradle
документ,Чтобы следующие совпадения:
apply plugin: 'com.android.model.library' model {
android {
compileSdkVersion = 23 buildToolsVersion = "23.0.1" defaultConfig.with {
minSdkVersion.apiLevel = 15 targetSdkVersion.apiLevel = 19
}
}
//Make sure to build with JDK version 7
compileOptions.with {
sourceCompatibility=JavaVersion.VERSION_1_7 targetCompatibility=JavaVersion.VERSION_1_7
}
android.buildTypes {
release {
minifyEnabled= false proguardFiles+= file('proguard-rules.pro')
}
}
}
Существовать и в то же время строить проект.
Что бы вы ни выбрали IDE , вы можете выполнить следующие действия: добавить собственный код Воля вприажатьприложениесередина:
ОткрытьPano.cpp
идобавить Следующий код; Наш код Воляпрохождения позже:
#include<jni.h>
#include<opencv2/core/core.hpp>
#include<opencv2/imgproc/imgproc.hpp>
#include<opencv2/features2d/features2d.hpp>
#include<vector>
extern"C" {
JNIEXPORT void JNICALL Java_com_app3_pano_PanoActivity_FindHarrisCorners(JNIEnv*, jobject, jlong addrGray, jlong addrRgba)
{
cv::Mat& mGr = *(cv::Mat*)addrGray;
cv::Mat& mRgb = *(cv::Mat*)addrRgba;
cv::Mat dst_norm;
cv::Mat dst = cv::Mat::zeros(mGr.size(),CV_32FC1);
//the size of the neighbor in which we will check
//the existence of a corner
int blockSize = 2;
//used for the Sobel kernel to detect edges before
//checking for corners
int apertureSize = 3;
// a free constant used in Harris mathematical formula
double k = 0.04;
//corners response threshold
float threshold=150;
cv::cornerHarris( mGr, dst, blockSize, apertureSize, k, cv::BORDER_DEFAULT );
cv::normalize( dst, dst_norm, 0, 255, cv::NORM_MINMAX, CV_32FC1, cv::Mat() );
for( unsignedint i = 0; i < dst_norm.rows; i++ )
{
float * row=dst_norm.ptr<float>(i);
for(int j=0;j<dst_norm.cols;j++)
{
if(row[j]>=threshold)
{
cv::circle(mRgb, cv::Point(j, i), 10, cv::Scalar(255,0,0,255));
}
}
}
}
}
нассуществоватьPanoActivity
добрыйсередина声明Понятнолокальная машинаметод,чтобы нативный код можно было вызвать позже:
public native void FindHarrisCorners(long matAddrGr, long matAddrRgba);
Мы объявляем нативные методы при создании нативной Библиотеки и существующих активностей.,Но да, когда мы пытаемся вызвать собственный метод,будет взимать платуприезжатьjava.lang.UnsatisfiedLinkError
,Потому что для еще не загрузил родную Библиотеку. для этого,нас Даже改onManagerConnected()
методксуществовать OpenCV Загрузите собственную библиотеку после инициализации:
private BaseLoaderCallback mLoaderCallback = new BaseLoaderCallback(this) {
@Override
public void onManagerConnected(int status) {
switch (status) {
case LoaderCallbackInterface.SUCCESS:
{
Log.i(TAG, "OpenCV loaded successfully");
// Load native library after(!) OpenCV initialization
System.loadLibrary("Pano");
} break;
default:
{
super.onManagerConnected(status);
} break;
}
}
};
Теперь мы готовим элемент меню «Использовать встроенную Библиотеку», чтобы вызвать встроенный угловой детектор Харриса. поэтому,Открытьres/menu/pano.xml
идобавить Следующие предметы:
<itemandroid:id="@+id/action_nativeHarris"
android:orderInCategory="2"
android:title="@string/action_nativeHarris">
</item>
существоватьPanoActivity
середина,Даже改onOptionsItemSelected()
киметь дело слокальная машина Состояние:
else if(id==R.id.action_nativeHarris)
{
if(sampledImage==null)
{
Context context = getApplicationContext();
CharSequence text = "You need to load an image first!";
int duration = Toast.LENGTH_SHORT;
Toast toast = Toast.makeText(context, text, duration);
toast.show();
return true;
}
Mat greyImage=new Mat();
Imgproc.cvtColor(sampledImage, greyImage, Imgproc.COLOR_RGB2GRAY);
FindHarrisCorners(greyImage.getNativeObjAddr(),sampledImage.getNativeObjAddr());
displayImage(sampledImage);
}
У нас есть список звонков Харрисугловой. Детектиз самостоятельно реализует необходимые шаги; Однако нам все равно нужно внимательно прочитать C++ Кодируйте детали, чтобы изучать то, что мы делаем, чтобы вы могли расширять и развивать Всуществовать идеи здесь. Конечно, с C++ Основная идея построения языка из Воля очень полезна.
наспервый Сумка含необходимыйизголовадокументсписок:
#include<opencv2/core/core.hpp>
#include<opencv2/imgproc/imgproc.hpp>
#include<opencv2/features2d/features2d.hpp>
#include<vector>
в соответствии сэтотимя约定Java_Fully_Qualified_Class_Name_MethodName
声明Мы Воляиспользоватьизфункция. нассуществоватьPanoActivity
середина声明изметодтолько采использоватьдва个参число:灰степеньицветизображениеизадрес; Однако собственный метод требует четырех. Первые два всегда существуют, какие-либо JNI метод声明серединаиспользовать。 Последние два соответствуют букве В, которую мы отправляем по адресу (существовать). Java серединаjlong
картографированиеприезжатьlong
):
JNIEXPORT void JNICALL Java_com_app3_pano_PanoActivity_FindHarrisCorners(JNIEnv*, jobject, jlong addrGray, jlong addrRgba)
нас Воля Ссылка отправлена наMat
ссылка,Один из них использует изображение в оттенках серого.,Другой использует Вцветное изображение:
cv::Mat& mGr = *(cv::Mat*)addrGray;
cv::Mat& mRgb = *(cv::Mat*)addrRgba;
Мы объявляем и инициализируем Волю, чтобы она использовала В для обнаружения угловых точек, переменных и списков:
cv::Mat dst_norm;
cv::Mat dst = cv::Mat::zeros(mGr.size(),CV_32FC1);
int blockSize = 2;
intapertureSize = 3;
double k = 0.04;
float threshold=150;
Мы Воля Харрисугловой детекторизместный实сейчассказатьдля“实сейчас”,и Волярогточкаиз响отвечать归один化для0
и255
между:
cv::cornerHarris( mGr, dst, blockSize, apertureSize, k, cv::BORDER_DEFAULT );
cv::normalize( dst, dst_norm, 0, 255, cv::NORM_MINMAX, CV_32FC1, cv::Mat() );
Мы нормализуем цикл существования из угловых точек и обнаруживаем местонахождение: рисуем круг в угловых точках на случай, если он реагирует на большое пороговое значение Vценить:
for( unsignedint i = 0; i < dst_norm.rows; i++ )
{
float * row=dst_norm.ptr<float>(i);
for(int j=0;j<dst_norm.cols;j++)
{
if(row[j]>=threshold)
{
cv::circle(mRgb, cv::Point(j, i), 10, cv::Scalar(255,0,0,255));
}
}
}
На фото слева даиспользовать Java Упаковкапрограммаиз HCD, на фото справа эта машина HCD
Приложение В режиме реального времени, скорость существования будет лучше из детектора. существуют В этом разделе мы опишем Воля FAST угловой Как работает детекториз.
позволятьнас考虑один个ПиксельP
。еслинассуществовать ПиксельP
изкруглый形Районсередина测试 16 пикселей, и среди них 12 个Пиксельизсила大Вили Маленький ВP
изсила加/уменьшатьa
,но说P
даодин个潜существоватьиз兴趣точкаилирог。 порог.
Этот процесс очень трудоемкий,поэтомудля Понятно加快Обнаружение速степень,предлагать Понятно另один种测试метод。 Долженалгоритмпервыйсуществоватьидентификация Расположение(1、9、5、13)только测试 4 пиксели; если Чтосередина三个大Вили Маленький ВP
изсила加/уменьшатьпорогценить,но继续другой 8 пиксели; В противном случае этот пиксель будет отброшен:
Воляк Внизменюэлементдобавить вприезжатьres/menu/pano.xml
:
<itemandroid:id="@+id/action_fast"
android:orderInCategory="4"
android:title="@string/action_fast">
</item>
ОткрытьPanoActivity
иредактироватьonOptionsItemSelected()
квключатьк Вниз Состояние:
else if(id==R.id.action_fast)
{
if(sampledImage==null)
{
Context context = getApplicationContext();
CharSequence text = "You need to load an image first!";
int duration = Toast.LENGTH_SHORT;
Toast toast = Toast.makeText(context, text, duration);
toast.show();
return true;
}
Mat greyImage=new Mat();
Imgproc.cvtColor(sampledImage, greyImage, Imgproc.COLOR_RGB2GRAY);
MatOfKeyPoint keyPoints=new MatOfKeyPoint();
FeatureDetector detector=FeatureDetector.create(FeatureDetector.FAST);
detector.detect(greyImage, keyPoints);
Features2d.drawKeypoints(greyImage, keyPoints, greyImage);
displayImage(greyImage);
}
Как упоминалось ранее, OpenCV Имеет очень простой интерфейс и заводской метод для создания различных детекторов. Детекторы Харриса FAST Единственная разница между изда, который мы отправляем в фабричный метод, заключается в следующих параметрах:
FeatureDetector detector = FeatureDetector.create(FeatureDetector.FAST);
Остальная часть кода точно такая же.
существовать本节середина,нас Воля КPanoActivity
добрыйдобавить Еще один нативный метод, называемый приезжать в нативной реализации Воли. FAST угловой детектор:
Откройте класс активности и добавьте следующее объявление:
public native void FindFastFeatures(long matAddrGr, long matAddrRgba);
Этот метод имеет два параметра. Первый — для изображения адреса в оттенках серого, второй — для цветной версии адреса.
Воляк Внизметоддобавить вприезжатьPano.cpp
документсередина:
JNIEXPORT void JNICALL Java_com_app3_pano_PanoActivity_FindFastFeatures(JNIEnv*, jobject, jlong addrGray, jlong addrRgba)
{
cv::Mat& mGr = *(cv::Mat*)addrGray;
cv::Mat& mRgb = *(cv::Mat*)addrRgba;
std::vector<cv::KeyPoint> v;
cv::FastFeatureDetector detector(50);
detector.detect(mGr, v);
for( unsignedint i = 0; i < v.size(); i++ )
{
const cv::KeyPoint& kp = v[i];
cv::circle(mRgb, cv::Point(kp.pt.x, kp.pt.y), 10, cv::Scalar(255,0,0,255));
}
}
существовать Переднийизкодсередина,наспервый实例化关键точкаиз К量ипорогценитьдля50
изFastFeatureDetector
объект,ипроходить传入灰степеньизображениеи关键точкаизнулевой К量Приходить调использоватьdetection
метод。 Затем,Мы рисуем круг для каждой обнаруженной ключевой точки прибытия.
нассуществоватьres/menu/pano.xml
серединадобавить Добавлен еще один пункт меню:
<itemandroid:id="@+id/action_nativefast"
android:orderInCategory="5"
android:title="@string/action_fastnative">
</item>
наконец,ОткрытьPanoActivity
иредактироватьonOptionsItemSelected()
к Сумка含к Вниз Состояние:
else if(id==R.id.action_nativefast)
{
if(sampledImage==null)
{
Context context = getApplicationContext();
CharSequence text = "You need to load an image first!";
int duration = Toast.LENGTH_SHORT;
Toast toast = Toast.makeText(context, text, duration);
toast.show();
return true;
}
Mat greyImage=new Mat();
Imgproc.cvtColor(sampledImage, greyImage, Imgproc.COLOR_RGB2GRAY);
FindFastFeatures(greyImage.getNativeObjAddr(),sampledImage.getNativeObjAddr());
displayImage(sampledImage);
}
На фото слева даиспользовать Java Упаковкапрограммаиз БЫСТРО, изображение справа — локальная машина FAST
OpenCV Labs из Еще один важный детектор,Также дескриптор,дадва个非常有имяноуже申пожалуйста专利изалгоритм(преобразование масштабно-инвариантного объекта(SIFT)иНадежные функции ускорения(SURF)из替代物 ORB。 хотетьиспользовать SIFT и СЕРФ, вы платите; Однако ОРБ Существование обеспечивает бесплатную выборку с точки зрения затрат на расчеты и соответствия производительности.
существуют В этом разделе мы обсуждаем Воля ORB из Обнаружениеустройствочасть. В основном это использование, которое мы существуем, рассмотренное в предыдущем разделе. FAST алгоритм и добавил следующие важные дополнения:
Воляк Внизменюэлементдобавить вприезжатьres/menu/pano.xml
:
<itemandroid:id="@+id/action_orb"
android:orderInCategory="6"
android:title="@string/action_orb">
</item>
наснуждатьсясуществоватьPanoActivity
добрыйсерединаредактироватьonOptionsItemSelected()
квключатьк Вниз Состояние:
else if(id==R.id.action_orb)
{
if(sampledImage==null)
{
Context context = getApplicationContext();
CharSequence text = "You need to load an image first!";
int duration = Toast.LENGTH_SHORT;
Toast toast = Toast.makeText(context, text, duration);
toast.show();
return true;
}
Mat greyImage=new Mat();
Imgproc.cvtColor(sampledImage, greyImage, Imgproc.COLOR_RGB2GRAY);
MatOfKeyPoint keyPoints=new MatOfKeyPoint();
FeatureDetector detector = FeatureDetector.create(FeatureDetector.ORB);
detector.detect(greyImage, keyPoints);
Features2d.drawKeypoints(greyImage, keyPoints, greyImage);
displayImage(greyImage);
}
существоватьдругойдетектор Переключаться между функциями очень легко. мы просто ORB из ID передача给factory
метод,Затем调использоватьdetect
метод。
существовать本节середина,Мы Воляиспользовать ORB Детектор реализован изначально, а этап предварительной обработки перенесен. CPP файл, чтобы JNI Стоимость звонка из снижается, если приезжать только за один звонок:
ОткрытьPanoActivity
добрыйидобавить Следующее заявление:
public native void FindORBFeatures(long matAddrRgba, int featuresNumber);
Этот метод принимает два параметра,Прямо сейчасместныйобъектизадресихотеть Обнаружениеизмаксимумособенностьчисло。
существоватьPano.cpp
середина,добавить Реализованы следующие методы:
JNIEXPORT void JNICALL Java_com_app3_pano_PanoActivity_FindORBFeatures(JNIEnv*, jobject, jlong addrRgba, jint featuresNumber)
{
cv::Mat& mRgb = *(cv::Mat*)addrRgba;
cv::Mat grayImg;
std::vector<cv::KeyPoint> v;
cv::cvtColor(mRgb,grayImg,cv::COLOR_RGBA2GRAY);
cv::OrbFeatureDetector detector(featuresNumber);
detector.detect(grayImg, v);
cv::drawKeypoints(grayImg,v,mRgb,cv::Scalar::all(-1),cv::DrawMatchesFlags::DRAW_RICH_KEYPOINTS);
}
нас Воля Воляцветизображение Конвертировать дляPano.cpp
из预иметь дело сшагруководить Понятноиметь дело с。 наспроходить调использоватьcv::cvtColor
ипередачавходитьизображение,Выходные изображения и код сопоставления для достижения. Затем,нас实例化один个ORBFeatureDetector
объект,Объектиз максимального количества признаков и т.д. В отправляем из параметров.
существовать Внизодин行,нас调использоватьdetect
метод。 наконец,насиспользоватьcv::drawKeypoints
методрисовать关键точка,и передать входное изображение (определить ключевые точки с помощью Визизображения),KeyPoint
из К量,Выходное изображение,использовать Врисовать关键точкаизцвет(using cv::Scalar::all(-1)
выражать (использоватьизцвет Волядаслучайныйиз),Последний знак да используется в качестве каждой ключевой точки и круга.,Его размер равен размеру характерной точки, а направление рисуется характерной точки.
Воляк Внизменюэлементдобавить вприезжатьres/menu/pano.xml
:
<itemandroid:id="@+id/action_nativeorb"
android:orderInCategory="7"
android:title="@string/action_orbnative">
</item>
наконец,ОткрытьPanoActivity
иредактироватьonOptionsItemSelected()
к Сумка含к Вниз Состояние:
else if(id==R.id.action_nativeorb)
{
if(sampledImage==null)
{
Context context = getApplicationContext();
CharSequence text = "You need to load an image first!";
int duration = Toast.LENGTH_SHORT;
Toast toast = Toast.makeText(context, text, duration);
toast.show();
return true;
}
Mat copy=sampledImage.clone();
FindORBFeatures(copy.getNativeObjAddr(),100);
displayImage(copy);
}
На фото слева даиспользовать Java Упаковкапрограммаиз ORB, картинка справа да имеет характерные пропорции и ориентацию, родные ORB
использовать Особенности Процесс изображения со второго шага и описание функции. Дескриптор С помощью функции «Вля» вам предоставляется дополнительная информация о достопримечательностях и существующих функциях обнаружения приезда, которые рассчитываются в местных районах/районах.
Вы можете следить за местностью по форме (прямоугольникили круг).,Режим выборки (плотная выборка,Чтосередина局部областьсерединаизвсе Пиксель Все Воляверноособенностьописыватьили Разреженная выборка способствует)верно Дескриптор функциируководитьточкадобрый ) и спектр (двоичный, который описывает вектор Волятолькодля 1 и 0 илииспользоватьлюбойскалярценитьилидругойценитьизскаляр)。
OpenCV Предоставляет различные виды продукции функции。 Да,существовать本节середина,потому что SIFT и Дескрипторы SURF (плотные и скалярные) — чтобы запатентовать алгоритм, за их использование нужно платить, поэтому мы только сосредоточимся на разреженном двоичном дескрипторе (также называемом локальным двоичным дескриптором).
использовать метод выборки пар пикселей,Независимо от формы дескриптора,может вычислять локальные двоичные дескрипторы,существоватьэтотметодсередина,сравнить выбранныеиз Пиксельвернокгенерироватьвыражатьописывать К量издвоичный字符串。 Например,еслинас有одинверно Пиксель(P1, P2)
,но比较P1
иP2
изсила。 еслиP1
изсила大ВP2
,но Воля1
放入описывать К量середина,в противном случае Воля Вставлять入0
。
Двоичные робастные независимые базовые характеристики(BRIEF)дескрипторпризнанныйдлядапредлагатьиз最简одинтакжеда Нет.один个местныйдвоичныйдескриптор。 для Понятноиспользовать长степеньдля N изописывать К量описывать兴趣точка,Алгоритмпрохождения несколькими случайными методами (единообразный,Гауссовскийждать)существовать31x31
色块областьсерединавыбирать Понятно N пары случайных пикселей и сравнивают их, чтобы построить двоичную строку.
для ORB, дескриптор проводит Воля точки интереса поворачивается в каноническое направление (при условии, что наш существующий этап обнаружения знает доминирующее направление точки интереса) Направление Воли добавить вприезжать BRIEF, затем вычисляет описание. В результате мы достигаем некоторой вращательной инвариантности. Например, если точка интереса доминирует в направлении 90 степень,носуществоватьиспользовать ORB Перед описанием точку интереса Воли и ее окрестности поворачивают так, чтобы она была направлена вверх (направление = 0), а затем описать интересующую точку, чтобы добиться инвариантности вращения.
для метода выборки пар пикселей, ORB В автономном режиме узнал, как объединять пиксели в пары, чтобы максимизировать дисперсию и уменьшить корреляцию, чтобы каждый пиксель вносил свой вклад в дескриптор. в Новая информация.
использовать метод рандомизации (BRIEF) или метод обучения из выборки (ORB) Выбор пар пикселей приводит к асимметричным формам дескрипторов,Как показано ниже:
Ключевые моменты: бинарный, надежный, инвариантный, масштабируемый(BRISK)Создание дескрипторасуществоватьк Четыре个同心环排列из 60 точек, поэтому форма выборки пары точек круглая и симметричная. каждыйточкапредставлятьодин个круглый形采样область(использовать Ввыбирать采样верно),По мере удаления от точки интереса,Площадь увеличится.
для вычисляет направление,использовать Гауссов Фильтр сглаживает каждую выбранную область, а затем вычисляет локальный градиент. Разделение выборки пополам разделено на две группы: длинные сегменты, расстояние между двумя парами велико до определенного порога, а локальный градиент используется вместе для расчета угла направления для направления точки интереса, обеспечивая при этом инвариантность вращения. Второй добрыйда короткий отрезок, в котором расстояние между двумя парами низкое В другом пороге ценить и сравнивать с Впроходить 512 построить 512 Битовый двоичный дескриптор. На картинке ниже описано BRISK Зона отбора проб:
Быстрые клавиши сетчатки(FREAK)дескрипторизкруглый形形状дабаза Влюдииз Видеть网膜система,Среди них центр плотности рецепторных клеток является самым высоким.,и随着нас离开и降低。 для режима выборки используйте алгоритм автономного обучения, чтобы изучить оптимальные пары пикселей, чтобы максимизировать дисперсию пар точек и самые корреляция миниатюризации.
После того, как вы определили соответствующий дескриптор для своих нужд, сразу же необходимо выбрать функцию расстояния, чтобы определить функций。 В зависимости от вашего дескриптора выбора, можно использовать множество функций расстояния. дляместныйдвоичныйособенность,любимыйизвыбиратьдаРасстояние Хэммингак测量два个ждать长двоичный字符串междуизразница。 Операция очень эффективна и быстра, поскольку для нее можно использовать инструкции машинного языка или XOR За операцией следует подсчет битов, которые необходимо выполнить.
существовать本частьсередина,Обновления нашей Воля,Чтобы можно было Воля смешивать разные детекторы с разными дескрипторами использовать,Чтобы найти жилье, подходящее по характеристикам.
Мы волясуществоватьприложениеменю в определении двух групп. Один с набором детекторов, а другой с набором дескрипторов. Мы также будемдобавить В пункте меню вы можете существовать там, где депутаты хотят найти проживание из объекта в данном сценарии. Открытьres/menu/pano.xml
идобавить вк Внизпроект:
<item android:orderInCategory="8" android:id="@+id/detector" android:title="@string/list_detector">
<menu><group android:checkableBehavior="single">
<item android:id="@+id/harris_check"
android:title="@string/action_harris"/>
<item android:id="@+id/fast_check"
android:title="@string/action_fast" android:checked="true"/>
<item android:id="@+id/orbD_check"
android:title="@string/action_orb" />
</group></menu>
</item>
<item android:orderInCategory="9" android:id="@+id/descriptor" android:title="@string/list_descriptor">
<menu><group android:checkableBehavior="single">
<item android:id="@+id/BRIEF_check"
android:title="@string/action_brief"/>
<item android:id="@+id/ORB_check"
android:title="@string/action_orb" android:checked="true"/>
<item android:id="@+id/BRESK_check"
android:title="@string/action_brisk"/>
<item android:id="@+id/FREAK_check"
android:title="@string/action_freak"/>
</group></menu>
</item>
<item android:id="@+id/action_match"
android:orderInCategory="10"
android:title="@string/action_match">
</item>
<item
android:id="@+id/action_selectImgToMatch"
android:orderInCategory="1"
android:showAsAction="never"
android:title="@string/action_selectImgToMatch"/>
Мы, Воля, следуем этой процедуре, чтобы найти приезжатьобъект в данном сценарии.первый,Загрузить сцену,Затем загрузите объектизображение,наконецвыбиратьmatch
。 хотеть执行匹配процесс,насредактироватьonOptionsItemSelected()
квключатьк Вниз Состояние:
else if(id==R.id.action_match)
{
if(sampledImage==null || imgToMatch==null)
{
Context context = getApplicationContext();
CharSequence text = "You need to load an object and a scene to match!";
int duration = Toast.LENGTH_SHORT;
Toast toast = Toast.makeText(context, text, duration);
toast.show();
return true;
}
int maximumNuberOfMatches=10;
Mat greyImage=new Mat();
Mat greyImageToMatch=new Mat();
Imgproc.cvtColor(sampledImage, greyImage, Imgproc.COLOR_RGB2GRAY);
Imgproc.cvtColor(imgToMatch, greyImageToMatch, Imgproc.COLOR_RGB2GRAY);
MatOfKeyPoint keyPoints=new MatOfKeyPoint();
MatOfKeyPoint keyPointsToMatch=new MatOfKeyPoint();
FeatureDetector detector=FeatureDetector.create(detectorID);
detector.detect(greyImage, keyPoints);
detector.detect(greyImageToMatch, keyPointsToMatch);
DescriptorExtractor dExtractor = DescriptorExtractor.create(descriptorID);
Mat descriptors=new Mat();
Mat descriptorsToMatch=new Mat();
dExtractor.compute(greyImage, keyPoints, descriptors);
dExtractor.compute(greyImageToMatch, keyPointsToMatch, descriptorsToMatch);
DescriptorMatcher matcher = DescriptorMatcher.create(DescriptorMatcher.BRUTEFORCE_HAMMING);
MatOfDMatch matches=new MatOfDMatch();
matcher.match(descriptorsToMatch,descriptors,matches);
ArrayList<DMatch> goodMatches=new ArrayList<DMatch>();
List<DMatch> allMatches=matches.toList();
double minDist = 100;
for( int i = 0; i < descriptorsToMatch.rows(); i++ )
{
double dist = allMatches.get(i).distance;
if( dist < minDist ) minDist = dist;
}
for( int i = 0; i < descriptorsToMatch.rows() && goodMatches.size()<maximumNuberOfMatches; i++ )
{
if(allMatches.get(i).distance<= 2*minDist)
{
goodMatches.add(allMatches.get(i));
}
}
MatOfDMatch goodEnough=new MatOfDMatch();
goodEnough.fromList(goodMatches);
Mat finalImg=new Mat();
Features2d.drawMatches(greyImageToMatch, keyPointsToMatch, greyImage, keyPoints, goodEnough, finalImg,Scalar.all(-1),Scalar.all(-1),new MatOfByte(), Features2d.DRAW_RICH_KEYPOINTS + Features2d.NOT_DRAW_SINGLE_POINTS);
displayImage(finalImg);
}
наспервыйубеждатьсясценаиобъектизображениеуженагрузка:
if(sampledImage==null || imgToMatch==null)
{
Context context = getApplicationContext();
CharSequence text = "You need to load an object and a scene to match!";
int duration = Toast.LENGTH_SHORT;
Toast toast = Toast.makeText(context, text, duration);
toast.show();
return true;
}
Волясценаиобъектизображение Все Конвертировать для灰степень:
Imgproc.cvtColor(sampledImage, greyImage, Imgproc.COLOR_RGB2GRAY);
Imgproc.cvtColor(imgToMatch, greyImageToMatch, Imgproc.COLOR_RGB2GRAY);
Детектор объекта строится согласно отприложению извыбирать, и его использование используется для обнаружения признаков в сцене и изображения объекта:
MatOfKeyPoint keyPoints=new MatOfKeyPoint();
MatOfKeyPoint keyPointsToMatch=new MatOfKeyPoint();
FeatureDetector detector = FeatureDetector.create(detectorID);
detector.detect(greyImage, keyPoints);
detector.detect(greyImageToMatch, keyPointsToMatch);
Мы выполняем одну и ту же операцию для всех типов дескрипторов. OpenCV Имеет детектороподобный интерфейс и интерфейс дескриптора. тысуществоватьDescriptorExtractor
добрыйначальство调использоватьодин个create
метод,И перейдите к использованию дескриптора ID. существуют В этом случае,ID база Внасотприложениеменюсередина Место做извыбирать。
DescriptorExtractor dExtractor = DescriptorExtractor.create(descriptorID);
Следующий,Мы существуем создать объект-дескриптор, для которого вызываем метод расчета и передаем изображение.,Обнаружениеприезжатьиз关键точкаинулевойизMat
объект Приходитьхранилищесуществоватьсценаиобъектизображениесередина Обнаружениеприезжатьизкаждыйособенностьизописывать,Описано хранилищем:
Mat descriptors=new Mat();
Mat descriptorsToMatch=new Mat();
dExtractor.compute(greyImage, keyPoints, descriptors);
dExtractor.compute(greyImageToMatch, keyPointsToMatch, descriptorsToMatch);
Затем,наспроходитьсуществоватьDescriptorMacther
добрыйначальство调использоватьcreate
методипередачатывыбиратьизdistance
функцияиз ID для создания объекта сопоставления. В нашем примере существует наш локальный двоичный дескриптор. поэтому,Расстояние Хэмминга Воляда Наши любимые извыбирать:
DescriptorMatcher matcher = DescriptorMatcher.create(DescriptorMatcher.BRUTEFORCE_HAMMING);
сейчассуществовать,нас Подготовитьпроходитьсуществовать匹配устройствообъектначальство调использоватьmatch
метод,и передайте описание функции объекта,сценаособенностьописыватьиDMatch
объектизнулевойматрица,отсценаиобъектизображениесерединапопытаться найтиприезжатьсоответствующие функции。 DMatch
объектдаодин个简одинизчисло据结构,использовать Вхранилищедва个匹配издескрипторирасстояние(существуем из примерадля Расстояние Хэмминга):
MatOfDMatch matches=new MatOfDMatch();
matcher.match(descriptorsToMatch,descriptors,matches);
Наконец, мы выбираем лучшие точки соответствия и рисуем их:
ArrayList<DMatch> goodMatches=new ArrayList<DMatch>();
List<DMatch> allMatches=matches.toList();
double minDist = 100;
for( int i = 0; i <descriptorsToMatch.rows(); i++ )
{
double dist = allMatches.get(i).distance;
if( dist < minDist ) minDist = dist;
}
for( int i = 0; i <descriptorsToMatch.rows() && goodMatches.size()<maximumNuberOfMatches; i++ )
{
if( allMatches.get(i).distance<= 2*minDist)
{
goodMatches.add(allMatches.get(i));
}
}
MatOfDMatch goodEnough=new MatOfDMatch();
goodEnough.fromList(goodMatches);
Mat finalImg=new Mat();
Features2d.drawMatches(greyImageToMatch, keyPointsToMatch, greyImage, keyPoints, goodEnough, finalImg,Scalar.all(-1),Scalar.all(-1),new MatOfByte(),Features2d.DRAW_RICH_KEYPOINTS + Features2d.NOT_DRAW_SINGLE_POINTS);
displayImage(finalImg);
использовать ORB Carry. Описание функций внешнего вида для масштабирования и вращения без изменений.
Мы видели, как приезжатьиспользовать Java Упаковкаустройство Обнаружение,описыватьисоответствующие функции。 Но да, если Воля эти шаги совместить, приезжать один JNI 调использоватьсередина会Даже快,Потому что для этого процесса требуется много шагов,и且каждыйшаг Все Конвертировать длявернолокальная машинакодизодин个 JNI вызов.
В этом разделе мы Волясуществоватьприложениеиз нативной стороны выполняет Обнаружение функции, описывающей процесс сопоставления.
Мы добавим новый пункт меню для выполнения собственных процессов. Открытьres/menu/pano.xml
идобавить вк Внизпроект:
<itemandroid:id="@+id/action_native_match"
android:orderInCategory="11"
android:title="@string/action_native_match">
</item>
существуют В этом разделе мы переносим процесс Воля Воля и этапы предварительной обработки на нативную сторону. оти Воляобщийиз JNI Сокращение накладных расходов маленький:
наспервыйсуществовать Активностьдобрыйсередина声明один个новыйизлокальная машинаметод。 Собственные методы ссылаются на изображение объекта, изображение сцены, детектор. ID и дескриптор ID и возвращает результаты с совпадающим изображением:
public native void FindMatches(long objectAddress, long sceneAddress,int detectorID, int descriptorID,long matchingResult);
нассуществоватьPano.cpp
документсерединаопределение Понятнолокальная машинаметод:
JNIEXPORT void JNICALL Java_com_app3_pano_PanoActivity_FindMatches(JNIEnv*, jobject, jlong objectAddress, jlong sceneAddress,jint detectorID, jint descriptorID,jlong matchingResult)
{
cv::Mat& object = *(cv::Mat*)objectAddress;
cv::Mat& scene = *(cv::Mat*)sceneAddress;
cv::Mat& result = *(cv::Mat*)matchingResult;
cv::Mat grayObject;
cv::Mat grayScene;
//Convert the object and scene image to grayscale
cv::cvtColor(object,grayObject,cv::COLOR_RGBA2GRAY);
cv::cvtColor(scene,grayScene,cv::COLOR_RGBA2GRAY);
std::vector<cv::KeyPoint> objectKeyPoints;
std::vector<cv::KeyPoint> sceneKeyPoints;
cv::Mat objectDescriptor;
cv::Mat scenceDescriptor;
//Construct a detector object based on the input ID
if(detectorID==1)//FAST
{
cv::FastFeatureDetector detector(50);
detector.detect(grayObject, objectKeyPoints);
detector.detect(grayScene, sceneKeyPoints);
}
else if(detectorID==5)//ORB
{
cv::OrbFeatureDetector detector;
detector.detect(grayObject, objectKeyPoints);
detector.detect(grayScene, sceneKeyPoints);
}
//Construct a descriptor object based on the input ID
if(descriptorID==3)//ORB
{
cv::OrbDescriptorExtractor descriptor;
descriptor.compute(grayObject,objectKeyPoints,objectDescriptor);
descriptor.compute(grayScene,sceneKeyPoints,scenceDescriptor);
}
else if(descriptorID==4)//BRIEF
{
cv::BriefDescriptorExtractor descriptor;
descriptor.compute(grayObject,objectKeyPoints,objectDescriptor);
descriptor.compute(grayScene,sceneKeyPoints,scenceDescriptor);
}
else if(descriptorID==5)//BRISK
{
cv::BRISK descriptor;
descriptor.compute(grayObject,objectKeyPoints,objectDescriptor);
descriptor.compute(grayScene,sceneKeyPoints,scenceDescriptor);
}
else if(descriptorID==6)//FREAK
{
cv::FREAK descriptor;
descriptor.compute(grayObject,objectKeyPoints,objectDescriptor);
descriptor.compute(grayScene,sceneKeyPoints,scenceDescriptor);
}
//Construct a brute force matcher object using the
//Hamming distance as the distance function
cv::BFMatcher matcher(cv::NORM_HAMMING);
std::vector< cv::DMatch> matches;
matcher.match( objectDescriptor, scenceDescriptor, matches);
//Select the best matching points and draw them
double min_dist = 100;
for( int i = 0; i < objectDescriptor.rows; i++ )
{
double dist = matches[i].distance;
if( dist < min_dist ) min_dist = dist;
}
std::vector< cv::DMatch> good_matches;
for( int i = 0; i < objectDescriptor.rows; i++ )
{
if( matches[i].distance <= 3*min_dist )
{
good_matches.push_back( matches[i]);
}
}
drawMatches( grayObject, objectKeyPoints, grayScene, sceneKeyPoints,good_matches, result, cv::Scalar::all(-1), cv::Scalar::all(-1),std::vector<char>(), cv::DrawMatchesFlags::NOT_DRAW_SINGLE_POINTS+cv::DrawMatchesFlags::DRAW_RICH_KEYPOINTS);
}
существовать Активностьдобрыйсередина,редактироватьonOptionsItemSelected()
квключатьк Вниз Состояние:
else if(id==R.id.action_native_match)
{
if(detectorID==FeatureDetector.HARRIS)
{
Context context = getApplicationContext();
CharSequence text = "Not a valid option for native matching";
int duration = Toast.LENGTH_SHORT;
Toast toast = Toast.makeText(context, text, duration);
toast.show();
return true;
}
if(sampledImage==null || imgToMatch==null)
{
Context context = getApplicationContext();
CharSequence text = "You need to load an object and a scene to match!";
int duration = Toast.LENGTH_SHORT;
Toast toast = Toast.makeText(context, text, duration);
toast.show();
return true;
}
Mat finalImg=new Mat();
FindMatches(imgToMatch.getNativeObjAddr(),sampledImage.getNativeObjAddr(),detectorID,descriptorID,finalImg.getNativeObjAddr());
displayImage(finalImg);
}
использовать ORB Сделайте нативное сопоставление для Обнаружение функциии Описание
Для обработки изображений используйте В, чтобы найти соответствие между изображениями, которые уже имеют определенную степень перекрытия изображений.
в целом,Сращивание делится на два этапа:
3x3
одинотвечатьматрица)из运动模форма,Должен模формаиспользовать ВВоляодин个изображениеизкоординироватькартографированиеприезжать另один个изображение。 После расширения приложения сращивания для использования двух или более изображений вы, Воля, начнете сталкиваться с полной регистрацией на доске. проблему и найдите набор согласованных параметров выравнивания, чтобы минимизировать рассогласование между всеми парами. Чтобы решить эту добрую проблему с помощью методов: регулировка луча (прогулкасамый уменьшает ошибку перепроецирования между каждой парой изображений для улучшения оценки) и wave Коррекция (конечный результат с помощью ВКоррекция, поскольку для обычно будет иметь волнистый эффект, обнаруживаемый в выходных данных существующей формы волны) панорамный.вернонас Приходить说Счастливая изда, OpenCV иstitcher
добрыйпучоксуществоватьодин起,Интерфейс «Добрый Воляпроход» очень прост для выполнения соединения проводов. Но да,OpenCV4Android SDK Не включено Java Реализатор упаковки, я думаю, для этого вам следует использовать привычный существовать приложение, использовать собственную реализацию, чтобы ее можно было расширить и добавить вприжать текущую версиюиз по другой причине. OpenCV Java Упаковка может удовлетворить ваши потребности. поэтому,Чтобы решить эту проблему,нас Воля КPano.cpp
добавить в另один个функцияк调использоватьstitcher
добрыйи返回результат。
Мы Волядобавить В новый изменён пункт для выполнения родного конвейера сшивания. Открытьres/menu/pano.xml
идобавить вк Внизпроект:
<item android:id="@+id/action_native_stitcher" android:orderInCategory="11" android:title="@string/action_native_stitch">
</item>
stitcher
существовать本节середина,нас Волядляместныйstitcher
добрый实сейчас Java Упаковкаустройство,к便Можетксуществоватьприложениесерединаиспользоватьэто:
наспервыйсуществоватьactivity
добрыйсередина声明один个новыйизлокальная машинаметод。 Нативный метод обращается к первой и второй сцене и возвращает результат с конкатенацией и изображением:
public native void Stitch(long sceneOneAddress, long sceneTwoAddress,long stitchingResult);
нассуществоватьPano.cpp
серединаопределение Понятноновыйиз拼接метод:
JNIEXPORTvoid JNICALL Java_com_app3_pano_PanoActivity_Stitch(JNIEnv*, jobject, jlong sceneOneAddress, jlong sceneTwoAddress,jlong stitchingResult) {
cv::Mat& sceneOne = *(cv::Mat*)sceneOneAddress;
cv::Mat& sceneTwo = *(cv::Mat*)sceneTwoAddress;
cv::Mat& result = *(cv::Mat*)stitchingResult;
/* The core stitching calls: */
//a list to store all the images that need to be stitched
std::vector<cv::Mat> natImgs;
natImgs.push_back(sceneOne);
natImgs.push_back(sceneTwo);
//create a stitcher object with the default pipeline
cv::Stitcher stitcher = cv::Stitcher::createDefault();
//stitch and return the result
stitcher.stitch(natImgs, result);
}
существовать Активностьдобрыйсередина,редактироватьonOptionsItemSelected
квключатьк Вниз Состояние:
else if(id==R.id.action_native_stitcher)
{
if(sampledImage==null || imgToMatch==null)
{
Context context = getApplicationContext();
CharSequence text = "You need to load an two scenes!";
int duration = Toast.LENGTH_SHORT;
Toast toast = Toast.makeText(context, text, duration);
toast.show();
return true;
}
Mat finalImg=new Mat();
Stitch(imgToMatch.getNativeObjAddr(),sampledImage.getNativeObjAddr(),finalImg.getNativeObjAddr());
displayImage(finalImg);
}
Мы видели, как приезжатьиспользоватьлокальная машинаи Java Детектор упаковки обнаруживает, описывает и сопоставляет различные характеристики. Кроме того, мы уже рассмотрели особенности проживания. изображенияиз Два вида приложений - может существовать одно, использов Пока они существуют, ищут место для проживания, еще один может Воля два Сшивка изображенийсуществоватьодин起кстроитьпанорамный.
существовать Внизодинглавасередина,Мы, Воля, переключаем передачи и освещаем тему машинного обучения из,и как научиться алгоритм обнаруживать жесты,И Воля — это автоматическое приложение для селфи с Встроить.
существовать本главасередина,нас Воляначинатьразвиватьновыйизприложение。 В приложениииз Цельда можно делать селфи, не касаясь экрана телефона. Ваше приложение Воля может обнаружить определенный жест, который запускает процесс сохранения текущего кадра камеры.
Мы, Воля, представляем из темы:
существуют В этом разделе мы обсуждаем Волямощныйиз Каскадный классификатор и его компоненты, Хаар особенность,积точкаизображение,адаптивный импульс(Adaboost)икаскадкстроитьодин个物体Обнаружениеустройство。
суммируя,Чтобы построить детектор объектов,ты Можеткиспользоватьтолько样本(Например,размердля24x24
излюди Лицо)игруз样本(любойдругой非люди Лицоизизображение)верно Чторуководитьтренироваться。 Вы, Воля, постоянно совершенствуете тренировочный процесс, чтобы минимизировать ошибки обучения (мин добрыйдля нелиц и з всего лиц и добры йдля лиц и з всего нелиц).
тренироваться Заканчиватьназад,Мы получили новое изображение,нас Требовать Обнаружениеустройство检查Чтода否有только面样本(Прямо сейчаслицо)。 Сделайте это, выполнив следующие шаги:
Характеристики, аналогичные Хааруде, являются еще одной хорошей характеристикой.,Используйте Â, чтобы распознавать лица, например,Пешеходы и другие твердые объекты.
Статья «Простая функция изусиливаскаскади быстрого обнаружения целей», автор: Paul Viola и Michael Jones В 2001 Предложено в 2001 году введение доброго Haar изособенностьизиспользоватьадаптивный импульсикаскад для распознавания лиц. от С тех пор многие другие характеристики и Усиливать вариации были использованы для создания многих других объектов доброго из подразделений доброго.
Учреждать Обнаружить из Каскадный с помощью Вобъекта классификатор Из Первый шаг — попытаться закодировать обширную информацию о положительных и отрицательных образцах. Другими словами, нам нужно определить, какие черты распознаются достаточно хорошо, чтобы отличить лица от нелиц. существуют В этом разделе мы обсуждаем Воля и Главу 5 глава,“App 3. Просмотрите функции проживания в «Полном просмотре просмотра» с различными типами функций. Здесь используются функции фиксированного размера из пиксельной сетки, существующие в данном случае, потому что что固定размеризсеткаопределение Понятноописыватьобласть,поэтому无需Обнаружение兴趣точка。
добрыйпохожий Haar из функций фиксированный размер пиксельной сетки, разделенной на черные и белые области, и существует 2 глава,“приложение Ядро свертки, обсуждаемое в разделе «1-Build Your Own Darkroom», очень похоже. 。 Воля Функцииприложение Вданныйизизображениеобластьчас,Соответствующую область можно описать путем вычитания суммы интенсивности пикселей под белой областью из общей интенсивности под черной областью.,от того, чтобы приехать по цене.
добрыйпохожий Haar гибкий дизайн функций; Например, вы можете иметь несколько Type 1 Характеристики, но да Воля разные по высоте и/или ширине приложение Визуализацияиз разных областей. Следовательно, учитывая эти параметры (характеристика добрый тип (1, 2, 3, 4 или 5),Ширина объекта,Высота объекта иприложение. Этот объект изображает область),Вы Воля получаете большое количество доступных описаний положительных и отрицательных образцов характеристик.
существовать Viola и Jones из工делатьсередина,Долженалгоритмиспользовать24x24
окноделатьдля Базовыйокноразмер(вселицои非лицоизразмер Все调整для24x24
Пиксель),Если учтены все параметры (добрый тип,(пропорции и расположение),нонас最终拥有размердля 160,000 Особенности бассейна.
На следующем рисунке показан пример пула компонентов функции:
С таким большим количеством функций Воля, такой алгоритм применения В реальном времени, приложение Воля является непростой задачей. Поэтому нам нужно начать делать некоторые оптимизации.
Технику оптимизации «адаптивный импульс» можно использовать для устранения избыточных функций или для того, чтобы правильно выделить подмножества функций; позже в этой главе Воля вернется к подробностям алгоритма.
另один种优化技术использовать Ввычислитьособенностьценить(Прямо сейчасотчерныйобластьсерединауменьшать去Белыйобласть),А добиться этого можно, рассчитав так называемое интегральное изображение.
Всякий раз, когда мы хотим вычислить функцию, оценить,Нам всем нужно, чтобы Воля добавлял белые пятна и из черных вычитал.,Тогда сделай это быстро,Viola и Jones Предлагается хороший трюк, называемый цельным изображением. следующее:
Точки изображения и входное изображение того же размера,Дакаждый积точка Пиксель(i, j)
дасуществоватьвходить Пиксель(i, j)
начальство方илевый侧извсевходить Пиксельизобщийи。 Например,когдалевыйначальство Пиксельиспользовать(0, 0)
索引час,ценить6
из整число Пиксель(1, 2)
давсевходить Пиксель(i, j)
изобщийи,Чтосерединаi <= 1
,j <= 2
。
После подсчета очков изображение,получатьизображениесерединалюбойобластьизвходить Пиксельизобщийи ВолястановитьсядляO(1)
Операция。
Например,考虑иметь Четыре个областьиз积точкаизображение:A
,B
,C
иD
。1
выражатьиз整число Пиксель Волявсевходить ПиксельизобщийихранилищесуществоватьобластьA
середина,2
выражатьиз整число ПиксельдаобластьA
иB
серединавсевходить Пиксельизобщийи,Зависит от3
выражатьиз整число ПиксельдаобластьA
серединавсевходить Пиксельизобщийи。C
иC
такой же,积точка Пиксель4
такой же,это们Волявходить ПиксельизобщийихранилищесуществоватьA
,B
,C
иD
。
сейчассуществовать,хотетьполучатьобластьD
серединавходить Пиксельизобщийи,Вам нужно всего лишь четыре целочисленных угловых пикселя 1, 2, 3 и 4 изценить.,ииспользовать简одиниз算术ОперацияD = 4 + 1 - 2 - 3
,Вы Воля получаете общую площадь ввода,Как показано ниже:
сейчассуществовать,У нас есть встроенные приемы для оптимизации расчетов функций.,наснуждатьсясамый Количество функций маленького использования.
дляэтот,Виола и Джонс используют алгоритм выбора Adaboost, который позволяет различать положительные и отрицательные образцы из связанных подмножеств признаков (также называемый слабым различителем добрый).,Как показано ниже:
В своей простейшей форме алгоритм Adaboost может описать следующее:
Как только у нас появятся эти функции (слабые разделители) и список,Затем их можно линейно объединить, чтобы сформировать более сильный сепаратор.,Он работает лучше, чем любой слабый анализатор.,наконец Конечноодин个порогценитьк最佳地Волялицои非лицоточка开。
дляхотетьточкадобрыйизновыйизображение,насвычислитьсуществоватьвходитьизображениеначальствоиспользовать Adaboost выбиратьизN
个Взаимно关особенностьизчисло量,И определить, человеческое ли это лицо или нет, исходя из выбранного порога.
наконецодин个技巧дляэтот种добрыйформаизточкадобрыйустройство起Понятноимя字,для ускорения обнаружения любого изображенияиз,Что依据данаснуждатьсяиспользоватьразмердля24x24
изокно扫描входитьизображение,Например Viola и Jones из работ. Да,мы знаем,существоватьмногоэтотдобрыйокносередина,Не существуетсуществоватьзаинтересованныйизобъект,поэтомунуждатьсяверноалгоритмруководить修改,Для того, чтобы как можно быстрее отбросить негативное окно и сосредоточиться на «Возможном из позитивного окна».
дляэтот,У нас есть серия мощных сепараторов,и不датренироватьсяодин个强точкадобрыйустройство。 Таким образом, все выбранные функции группируются по нескольким этапам, где каждый этап определяет с помощью В, что данное окно содержит интересующий объект из отрицательного окна или возможное окно. по сути,Это обновление позволяет нам как можно раньше устранить большое количество негативных окон, использовать меньший набор сопутствующих характеристик.,Как показано ниже:
После тренировочного процесса Заканчивать,У нас есть ряд мощных сепараторов,Эти добрые разделители могут существовать в любом изображении в приложении фиксированного размера в окне слайдов.,И определим, содержит ли данное окно интересный изобъект:
существуют в следующей части,Мы Воляиспользоватьуже经тренироватьсяхорошийиз Каскадный классификатор,Этот анализатор может обнаружить сомкнутые ладони на рисунке.,А Воля закрывает ладонь и сохраняет текущий кадр.
существовать本节середина,Мы Воляиспользовать Каскадный классификатор для обнаружения закрытых ладоней в кадре камеры телефона, но сначала мы, Воля, познакомим вас с тем, как его использовать. OpenCV Получите доступ к камере вашего телефона.
Мы, Воля, выполняем те же действия, что и в предыдущей главе.,первыйсоздаватьодин个имядляAutoSelfie
изнулевой白Активностьновыйприложение。
для Чтобы приложение могло получить доступ к изкамере телефона и сохранять изображения, вам необходимы следующие два разрешения:
<uses-permissionandroid:name="android.permission.CAMERA"/>
<uses-permissionandroid:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
Вы можете использовать эту главу вместе с пакетом кода, чтобы найти остальную часть конфигурации.
OpenCV для Предварительный просмотр камерыдобрыйпоставлять Java Реализация, добрый обрабатывается оборудованием камеры. OpenCV Библиотека взаимодействия. org.opencv.android.JavaCameraView
добрыйделатькамера Можетксуществоватьоборудование屏幕начальствоиметь дело сирисоватьрамка。
приезжатьв настоящий моментдляконец,использоватьJavaCameraView
Предварительный просмотр кадров камеры достаточно; Да,Нам, Воля, нужно определиться самому изкамера посмотреть добрый,к便кназад能够РасширятьJavaCameraView
добрыйиз Функция。Теперь существуем, давайте посмотрим, как определить себя изкамера, вид добрый:
создаватьодин个имядляcom.app4.autodselfie.CamView
изновый Java добрый。
делатьновыйдобрый Расширятьприезжатьorg.opencv.android.JavaCameraView
。
如ВнизопределениеCamView
добрый构造устройство:
public CamView(Context context, AttributeSet attrs) {
super(context, attrs);
}
Вот и все. Позже, когда мы перешли к приложению в добавлении В функции фотографирования мы, Воля, возвращаемся, приезжая этот добрый.
существоватьприложениемакетдокументactivity_auto_selfie.xml
середина,нас Воля主ВидетькартинаопределениедляCamView
добрый(потому чтодляэтодаandroid.view.SurfaceView
добрыйизребенокдобрый):
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.app4.autoselfie.CamView
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:id="@+id/auto_selfie_activity_surface_view"/>
</LinearLayout>
返回АктивностьAutoSelfie
,Мы, Воля, выполняем следующие действия, чтобы начать с устройства с камеры получать кадры:
Даже改Активностьдобрыйк实сейчасCvCameraViewListener2
интерфейс,Интерфейс会Воля Активностьдобрыйизменять Изменятьдляиспользовать В监听насCamView
добрый,камераView Start,Взгляд останавливается и получает три жизненных события:
public class AutoSelfie extends Activity implements CvCameraViewListener2
нас声明два个нулевойизMat
объект-один个использовать Вдержатькогда前камерарамкаиз RGB Версия, еще одно сохранение версии в оттенках серого с буквой В:
private Mat mRgba;
private Mat mGray;
нас实сейчас ПонятноCvCameraViewListener2
из三个缺失事件иметь дело сустройство。 начинать摄影机Видетькартинаназад,нас Воляинициализациядва个Mat
объект。 Когда вид камеры прекращается, мы отпускаем ее, а когда начинаем получать кадры камеры, возвращаем текущий кадр для отрисовки на экране. RGB Версия:
public void onCameraViewStarted(int width, int height) {
mGray = new Mat();
mRgba = new Mat();
}
public void onCameraViewStopped() {
mGray.release();
mRgba.release();
}
public Mat onCameraFrame(CvCameraViewFrame inputFrame) {
mRgba=inputFrame.rgba();
return mRgba;
}
ДаженовыйonCreate()
методкпопытаться найтиприезжатьнассуществоватьприложениемакетдокументсерединаопределениеизCamView
объект,Волякамеранастраиватьдлясоединять(только面илиназад),существовать本例середина,Мы Воляконнекткфронтальная камера,наконец,Волянасиз АктивностьзарегистрироватьсядляCamView
объектжизненные событияиз监听устройство:
mOpenCvCameraView = (CamView) findViewById(R.id.auto_selfie_activity_surface_view);
mOpenCvCameraView.setCameraIndex(1);
mOpenCvCameraView.setCvCameraViewListener(this);
наконец,Библиотека OpenCV успешно загружена,нас МожеткделатьCamView
объектConnect приезжать в камеру устройства; Только有этот样onCameraViewStarted()
才会被调использовать,CamView
объект Изменятьдля Активность状态:
private BaseLoaderCallback mLoaderCallback = new BaseLoaderCallback(this) {
@Override
public void onManagerConnected(int status) {
switch (status) {
case LoaderCallbackInterface.SUCCESS:
{
Log.i(TAG, "OpenCV loaded successfully");
mOpenCvCameraView.enableView();
} break;
default:
{
super.onManagerConnected(status);
} break;
}
}
};
ты会Уведомлениеприезжать,Воля При вертикальном положении устройства,рисоватьиз框架会翻изменять。 Не волнуйтесь, мы позаботимся об этом позже.
Приложение «Автоматическое селфи» Следующее предложение обнаружения для захвата текущего кадра камеры. я发сейчас,Близко к ладони, достаточно хороших советов,Вы можете рассмотреть другие советы,Например笑Лицождать。
только如нассуществовать“Каскадный в разделе "классификатор" упомянуто перемещениеиз, наш детектор Волядаиспользоватьдобрый аналогичен Haar Особенности из Каскадный классификатор。
Хорошо обученный на этапе и выбранные характеристики Воля сохраняет существование XML в файле. ты Можеткпрямойотэта страницаскачатьдокумент,Вы также можете найти документ «Прибытие» в папке «Изпроект-документ», прилагаемой к этой главе.
один旦делатьтренироваться有素източкадобрыйустройство Обнаружениеприезжатьтывыбиратьизобъект(существуем из-за случаядлязакрытая ладонь),OpenCV предоставит многомасштабный детектор раздвижных окон,Это Волясуществовать раздвижное окно средний проект хорошо обученный из перегородки добрый,И существуют входные изображенияиз в нескольких масштабах,Возвращает ограничивающую рамку вокруг обнаруженного объекта выезда в разных масштабах.
Глава нашего существования № 5 «Приложение 3: Полный просмотр просмотра» столкнулась с концепцией создания изображения пирамиды, построенной с использованием нескольких масштабов.
использоватьorg.opencv.objdetect.CascadeClassifier
добрыйделатьдлясейчасстановитьсяизслайдокно Обнаружениеустройство非常容易。 Сначала нам нужен хорошо обученный сепаратор XML документкопироватьприезжатьприложение原始ресурсдокументпапка\res\raw\haarhand.xml
середина.
Следующий,наспроходить如Вниз Даже改BaseLoaderCallback
实сейчас Приходить声明иинициализацияorg.opencv.objdetect.CascadeClassifier
объект:
private File cascadeFile;
private CascadeClassifier cascadeClassifier;
private BaseLoaderCallback mLoaderCallback = new BaseLoaderCallback(this) {
@Override
public void onManagerConnected(int status) {
switch (status) {
case LoaderCallbackInterface.SUCCESS:
{
Log.i(TAG, "OpenCV loaded successfully");
try {
// load cascade file from application resources
InputStream is = getResources().openRawResource(R.raw.haarhand);
File cascadeDir = getDir("cascade", Context.MODE_PRIVATE);
cascadeFile = new File(cascadeDir, "haarhand.xml");
FileOutputStream os = new FileOutputStream(cascadeFile);
byte[] buffer = new byte[4096];
int bytesRead;
while ((bytesRead = is.read(buffer)) != -1) {
os.write(buffer, 0, bytesRead);
}
is.close();os.close();
//Initialize the Cascade Classifier object using the
// trained cascade file
cascadeClassifier = new CascadeClassifier(cascadeFile.getAbsolutePath());
if (cascadeClassifier.empty()) {
Log.e(TAG, "Failed to load cascade classifier");
cascadeClassifier = null;
} else
Log.i(TAG, "Loaded cascade classifier from " + cascadeFile.getAbsolutePath());
cascadeDir.delete();
} catch (IOException e) {
e.printStackTrace();
Log.e(TAG, "Failed to load cascade. Exception thrown: " + e);
}
mOpenCvCameraView.enableView();
} break;
default:
{
super.onManagerConnected(status);
}
break;
}
}
};
сейчассуществовать,Мы готовы обработать каждый кадр камеры,для обнаружения закрытых ладоней и автоматической съемки фотографий.
Наша Воляиспользоватьзалгоритм Подведите итоги ниже:
100x100
Пиксельизнулевой间хранилищеведро,Каждая ограничивающая рамка размещается в соответствии с ее положением, соответствующим местоположению хранилища в пространстве хранилища середина.N
个рамка之назад,нас检查да否有один个Сумка含N
граница框изхранилищеведро。 этотиметь в видудляN
个连续рамка Приходить说,Обнаружение стабильности,поэтому,Вероятность ложных срабатываний очень мала.Чтобы начать реализацию этого алгоритма,наспервыйнуждаться Даже改CamView
добрыйк实сейчасandroid.hardware.Camera.PictureCallback
,к便дляonPictureTaken()
метод обратного вызовапоставлять实сейчаскдержатьданныйизкамерарамка。
новыйизCamView
добрый Как показано ниже:
public class CamView extends JavaCameraView implements PictureCallback {
private static final String TAG = "AutoSelfie::camView";
private String mPictureFileName;
public CamView(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public void onPictureTaken(byte[] data, Camera camera) {
Log.i(TAG, "Saving a bitmap to file");
// The camera preview was automatically stopped. Start it
// again.
mCamera.startPreview();
mCamera.setPreviewCallback(this);
// Write the image in a file (in jpeg format)
try {
FileOutputStream fos = new FileOutputStream(mPictureFileName);
fos.write(data);
fos.close();
} catch (java.io.IOException e) {
Log.e("PictureDemo", "Exception in photoCallback", e);
}
}
public void takePicture(final String fileName) {
Log.i(TAG, "Taking picture");
this.mPictureFileName = fileName;
// Postview and jpeg are sent in the same buffers if the
//queue is not empty when performing a capture.
// Clear up buffers to avoid mCamera.takePicture to be stuck
//because of a memory issue
mCamera.setPreviewCallback(null);
// PictureCallback is implemented by the current class
mCamera.takePicture(null, null, this);
}
}
Если у вас есть функция сохранения кадра камеры,Сразу Можеткпроходить Даже改onCameraFrame()
из实сейчас Приходить ДаженовыйAutoSelfie
Действия добрый, чтобы обнаружить закрытые ладони:
public Mat onCameraFrame(CvCameraViewFrame inputFrame) {
//Flip around the Y axis
Core.flip(inputFrame.rgba(), mRgba, 1);
Core.flip(inputFrame.gray(),mGray,1);
if (mAbsoluteFaceSize == 0) {
int height = mGray.rows();
if (Math.round(height * mRelativeFaceSize) > 0) {
mAbsoluteFaceSize = Math.round(height * mRelativeFaceSize);
}
}
MatOfRect closedHands = new MatOfRect();
if (cascadeClassifier != null)
cascadeClassifier.detectMultiScale(mGray, closedHands, 1.1, 2, 2,new Size(mAbsoluteFaceSize, mAbsoluteFaceSize), new Size());
Rect[] facesArray = closedHands.toArray();
for (int i = 0; i < facesArray.length; i++)
{
Core.rectangle(mRgba, facesArray[i].tl(), facesArray[i].br(), HAND_RECT_COLOR, 3);
Point quatnizedTL=new Point(((int)(facesArray[i].tl().x/100))*100,((int)(facesArray[i].tl().y/100))*100);
Point quatnizedBR=new Point(((int)(facesArray[i].br().x/100))*100,((int)(facesArray[i].br().y/100))*100);
int bucktID=quatnizedTL.hashCode()+quatnizedBR.hashCode()*2;
if(rectBuckts.containsKey(bucktID))
{
rectBuckts.put(bucktID, rectBuckts.get(bucktID)+1);
rectCue.put(bucktID, new Rect(quatnizedTL,quatnizedBR));
}
else
{
rectBuckts.put(bucktID, 1);
}
}
int maxDetections=0;
int maxDetectionsKey=0;
for(Entry<Integer,Integer> e : rectBuckts.entrySet())
{
if(e.getValue()>maxDetections)
{
maxDetections=e.getValue();
maxDetectionsKey=e.getKey();
}
}
if(maxDetections>5)
{
Core.rectangle(mRgba, rectCue.get(maxDetectionsKey).tl(), rectCue.get(maxDetectionsKey).br(), CUE_RECT_COLOR, 3);
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss");
String currentDateandTime = sdf.format(new Date());
String fileName = Environment.getExternalStorageDirectory().getPath() + "/sample_picture_" + currentDateandTime + ".jpg";
mOpenCvCameraView.takePicture(fileName);
Message msg = handler.obtainMessage();
msg.arg1 = 1;
Bundle b=new Bundle();
b.putString("msg", fileName + " saved");
msg.setData(b);
handler.sendMessage(msg);
rectBuckts.clear();
}
return mRgba;
}
Лучше Мы объясняем код киз шаг за шагом:
нассуществовать y Переверните поле ввода по оси, чтобы устранить эффект зеркального отображения:
//Flip around the Y axis
Core.flip(inputFrame.rgba(), mRgba, 1);
Core.flip(inputFrame.gray(),mGray,1);
Рассчитайте размер самого маленького объекта, исходя из высоты поля ввода:
if (mAbsoluteFaceSize == 0) {
int height = mGray.rows();
if (Math.round(height * mRelativeFaceSize) > 0) {
mAbsoluteFaceSize = Math.round(height * mRelativeFaceSize);}}
нассуществовать Каскадный классификаторобъектначальство调использоватьdetectMultiScale()
метод Приходитьстроитьизображениепирамидаисуществоватькаждый Пропорция尺начальствобегатьслайдокно Обнаружениеустройство:
MatOfRect closedHands = new MatOfRect();
if (cascadeClassifier != null)
cascadeClassifier.detectMultiScale(mGray, closedHands, 1.1, 2, Objdetect.CASCADE_SCALE_IMAGE,new Size(mAbsoluteFaceSize, mAbsoluteFaceSize), new Size());
насиспользоватьк Вниз参число调использоватьdetectMultiScale()
:
MatOfRect
объект,Обнаружение прибытияиз ограничивающих рамок с Вхранилищами1.1
иметь в виду Волякогда前Пропорцияуменьшать少 10% Построить следующую шкалу в пирамиде, имеющую более высокую оценку, значит быстрее провести расчеты) Если масштабирование существования не закрывает ладонь при определенных размерах, распознавание лицевой стороны может быть потеряно ценить)flagCASCADE_SCALE_IMAGE
Увеличитьизображениекстроитьизображениепирамида (Потому что для есть другой способ ввести характеристики масштаба для обнаружения различных масштабов и объектов) Следовательно, для Чтобы улучшить производительность и упростить операции, мы Воля придерживаемся существующих 5 глава,“приложение 3- Просмотр панорам"После наличия списка обнаружения,нас希望Воляэто们точка组дляразмердля100 x 100
Пиксельизнулевой间точка区,Стабильное обнаружение и устранение ложных срабатываний при использовании различных кадров:
Rect[] facesArray = closedHands.toArray();
for (int i = 0; i < facesArray.length; i++){
//draw the unstable detection using the color red
Core.rectangle(mRgba, facesArray[i].tl(), facesArray[i].br(), HAND_RECT_COLOR, 3);
//group the detections by the top-left corner
Point quatnizedTL=new Point(((int)(facesArray[i].tl().x/100))*100,((int)(facesArray[i].tl().y/100))*100);
//group the detections by the bottom-right corner
Point quatnizedBR=new Point(((int)(facesArray[i].br().x/100))*100,((int)(facesArray[i].br().y/100))*100);
//get the spatial bucket ID using the grouped corners hashcodes
int bucktID= quatnizedTL.hashCode()+quatnizedBR.hashCode()*2;
//add or increase the number of grouped detections per bucket
if(rectBuckts.containsKey(bucktID)){
rectBuckts.put(bucktID, rectBuckts.get(bucktID)+1);
rectCue.put(bucktID, new Rect(quatnizedTL,quatnizedBR));
}
else{
rectBuckts.put(bucktID,1);
}
}
Мы устанавливаем порог прибытия объектов в рамки, чтобы указать на стабильное обнаружение. Если количество кадров превышает порог ценить, сохраните текущий кадр:
int maxDetections=0;
int maxDetectionsKey=0;
for(Entry<Integer,Integer> e : rectBuckts.entrySet()){
if(e.getValue()>maxDetections){
maxDetections=e.getValue();
maxDetectionsKey=e.getKey();
}
}
//Threshold for a stable detection
if(maxDetections>5){
//Draw the stable detection in green
Core.rectangle(mRgba, rectCue.get(maxDetectionsKey).tl(), rectCue.get(maxDetectionsKey).br(), CUE_RECT_COLOR, 3);
//build the file name
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss");
String currentDateandTime = sdf.format(new Date());
String fileName = Environment.getExternalStorageDirectory().getPath() +"/sample_picture_" + currentDateandTime + ".jpg";
//take the picture
mOpenCvCameraView.takePicture(fileName);
//show a notification that the picture is saved
Message msg = handler.obtainMessage();msg.arg1 = 1;
Bundle b=new Bundle();b.putString("msg", fileName + " saved");
msg.setData(b);handler.sendMessage(msg);
//clear the spatial buckets and start over
rectBuckts.clear();
}
return mRgba;
}
В этой главе мы создали новое приложение, основанное на известном каскадном инструменте Build, который может автоматически делать фотографии. Мы уже рассмотрели особенности проживанияотиспользоватьиз. импульсизучатьалгоритмикаскадстроить Каскадный классификаторизпроцесс。 ты还изучать Понятно如何использовать经过тренироватьсяизточкадобрыйустройство Приходитьинициализацияииспользоватьбаза В多尺степеньслайдокноиз Обнаружениеустройство,распознавать жесты закрытия ладони,И Воля эти обнаружения делает для подсказки с устройства и зкамеры захвата кадров.