Este tutorial se divide en 2 partes:
1. En la PRIMERA se crea un proyecto de Android Studio para utilizar C++, se configura Opencv 3.4.0 y se llama unas funciones de C++ que devuelven Strings
2. En la SEGUNDA se utiliza el proyecto de la PRIMERA, se configura la cámara para leer video, se llama a una función de procesamiento de Opencv en C++ que recibe y convierte cada cuadro del video a escala de grises.
El detalle es el siguiente:
#######################
PRIMERA PARTE Configurar un proyecto en Android Studio para ejecutar una funcion de C++ que utiliza Opencv
#######################
El siguiente es el tutorial en el que me base, el video puede ayudar a resolver algunas dudas
Tutorial 1. Android Compile OpenCv Native C++
https://www.youtube.com/watch?v=Vp20EdU5qjU
NOTA: Para usuarios de Windows la dirección de carpeta
/home/nombreusuario/ equivale a C:/users/nombreusuario/
Requiere:
Android Studio instalado en su PC
SDK de Opencv para Android descargado y descomprimido, puede descargar la version 3.4.0 utilizada en este tutorial desde:
https://sourceforge.net/projects/opencvlibrary/files/opencv-android/3.4.0/
CREAR PROYECTO ANDROID para C++
Crear Nuevo Proyecto de Android
- Marcar Include C++ Support
- En Customize C++ support
-- Seleccionar C++11
-- Marcar Exceptions Support
-- Marcar Runtime Type Information Support
IMPORTAR EL MODULO DE OpenCV
Menu File/New/Import Module ... (1:50 video)
- En source directory, seleccionar de la carpeta donde se descomprime el Opencv la que dice java, ejemplo: /home/nombreusuario/OpenCV-3.4.0-android-sdk/sdk/java
- Seleccionar el Module Name: openCVLibrary3.4.0
- Finish
CONFIGURAR build.gradle de la "app" y del "modulo"
NOTA: Ambos archivos deben coincidir
Cambiar el modo de visualización del explorador de archivos de android. De Android a Project Files (3:02)
- Seleccionar carpeta app, buscar build.gradle y abrir archivo
- Seleccionar carpeta openCvLibrary340, buscar build.gradle y abrir archivo
- Los valores siguientes deben coincidir en los 2 archivos, sino corregir
######### Esta es la configuración de mis 2 archivos build.gradle
compileSdkVersion 26
buildToolsVersion "27.0.3"
defaultConfig {
minSdkVersion 19
targetSdkVersion 26
}
#########
AGREGAR bibliotecas nativas (libraries), (3:10 video)
- Crear nuevo directorio con el nombre "jniLibs" dentro de /src/main
- Pegar archivos de carpeta de OpenCV Android SDK /home/nombreusuario/OpenCV-3.4.0-android-sdk/sdk/native
CONFIGURAR dependencias del módulo
- Cambiar el modo de visualización del explorador de archivos de android de Project Files a Android (3:55)
- Seleccionar "app" con del botón derecho del mouse seleccionar Open Module Settings
-- Seleccionar pestaña Dependencies y presionar boton con signo + color verde para agregar la dependencia
-- Seleccionar openCVLibrary340
CONFIGURAR CMakeLists.txt para compilar archivos C++ (4:23 video)
- Modificar las rutas correctas de las Carpetas de tu proyecto
- Se anexa el código completo de mi proyecto
# For more information about using CMake with Android Studio, read the
# documentation: https://d.android.com/studio/projects/add-native-code.html
################# Agregado
set(pathToProject /home/nombreusuario/AndroidStudioProjects/NombreProyecto)
set(pathToOpenCv /home/nombreusuario/OpenCV-3.4.0-android-sdk)
#################
# Sets the minimum version of CMake required to build the native library.
cmake_minimum_required(VERSION 3.4.1)
################# Agregado
set (CMAKE_VERBOSE_MAKEFILE on)
set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=gnu++11")
include_directories(${pathToOpenCv}/sdk/native/jni/include)
#################
# Creates and names a library, sets it as either STATIC
# or SHARED, and provides the relative paths to its source code.
# You can define multiple libraries, and CMake builds them for you.
# Gradle automatically packages shared libraries with your APK.
add_library( # Sets the name of the library.
native-lib
# Sets the library as a shared library.
SHARED
# Provides a relative path to your source file(s).
src/main/cpp/native-lib.cpp )
################# Agregado
add_library( lib_opencv SHARED IMPORTED)
set_target_properties(lib_opencv PROPERTIES IMPORTED_LOCATION ${pathToProject}/app/src/main/jniLibs/${ANDROID_ABI}/libopencv_java3.so)
#################
# Searches for a specified prebuilt library and stores the path as a
# variable. Because CMake includes system libraries in the search path by
# default, you only need to specify the name of the public NDK library
# you want to add. CMake verifies that the library exists before
# completing its build.
find_library( # Sets the name of the path variable.
log-lib
# Specifies the name of the NDK library that
# you want CMake to locate.
log )
# Specifies libraries CMake should link to your target library. You
# can link multiple libraries, such as libraries you define in this
# build script, prebuilt third-party libraries, or system libraries.
################# Agregado
target_link_libraries( native-lib $\{log-lib} lib_opencv)
#################
AGREGAR librería "opencv_java3" en archivo MainActivity.java dentro de "static"
NOTA: Al crear un proyecto nuevo con soporte para C++ agrega varias líneas por defecto
- Debe quedar de la siguiente manera
static {
System.loadLibrary("native-lib"); //native-lib es el archivo C++ native-lib.cpp
System.loadLibrary("opencv_java3");
}
MODIFICAR método OnCreate de archivo MainActivity.java
- Debe quedar :
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Example of a call to a native method
TextView tv = (TextView) findViewById(R.id.sample_text);
tv.setText(stringFromJNI());
if (!OpenCVLoader.initDebug()){
tv.setText(tv.getText() + "\n OpenCv no Funciona");
}else{
tv.setText(tv.getText() + "\n OpenCv Funciona");
tv.setText(tv.getText() + "\n" + validate());
}
}
- Al final de MainActivity.java deben quedar las siguientes llamadas a las funciones de C++
/**
* A native method that is implemented by the 'native-lib' native library,
* which is packaged with this application.
*/
public native String stringFromJNI(); // Encabezado de función en C++
public native String validate(); // Encabezado de función en C++
EDITAR código de C++, editar y crear funciones
- Dentro de la carpeta app/cpp/native-lib se encuentra el archivo native-lib.cpp que se creo cuando inicio el nuevo proyecto
- Agregar la nueva función validate(), el código del archivo native-lib.cpp debe quedar:
NOTA: En el código siguiente, los nombres de las funciones "_com_apps_jjgarsal" corresponden a la ruta del proyecto en Android y "opencvwithc" es el nombre del proyecto en minúsculas, cambiar por los datos de tu proyecto
#include <jni.h>
#include <string>
#include <opencv2/core.hpp>
extern "C" {
JNIEXPORT jstring
JNICALL
Java_com_apps_jjgarsal_opencvwithc_MainActivity_stringFromJNI(
JNIEnv *env,
jobject/* this */) {
std::string hello = "Hello from C++ En Opencv";
return env->NewStringUTF(hello.c_str());
}
JNIEXPORT jstring
Java_com_apps_jjgarsal_opencvwithc_MainActivity_validate(
JNIEnv * env, jobject/* this */) {
cv::Rect();
cv::Mat(); // Esta línea crea una matriz con opencv
std::string hello2 = "Hello from validate";
return env -> NewStringUTF(hello2.c_str()) ;
}
PROBAR APLICACIÓN
Al ejecutar la aplicación en el celular se mostraran los mensajes "Hello from C++ En Opencv" y "Hello from validate"
#######################
SEGUNDA PARTE Leer imagen de cámara del dispositivo móvil y convertirla a escala de grises con Android Studio, llamando funciones C++ que utiliza Opencv
#######################
Basado en el tutorial "Using C++ OpenCV code with Android"
http://www.jayrambhia.com/blog/ndk-android-opencv
https://github.com/jayrambhia/nativecodeGray (código fuente original)
Utilizando el tutorial de la PRIMERA PARTE se agrega la funcionalidad de la cámara, es decir, el proyecto (de la PRIMERA PARTE) ya se encuentra creado y configurado para utilizar Opencv desde C++
CONFIGURANDO AndroidManifest.xml para agregar permisos de uso de la cámara
- Agregar permisos en archivo AndroidManifest.xml
<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"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
EDITAR archivo de layout activity_main.xml
NOTA: "com.apps.jjgarsal.opencvwithc" corresponde a la ruta y nombre del proyecto mio, hay que modificar para que corresponda con el tuyo
- Debe quedar como sigue el archivo activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:opencv="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.apps.jjgarsal.opencvwithc.MainActivity">
<TextView
android:id="@+id/sample_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal">
<org.opencv.android.JavaCameraView
android:id="@+id/opencv_part_native_surface_view"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:visibility="gone"
opencv:camera_id="any"
opencv:show_fps="true" />
<org.opencv.android.JavaCameraView
android:id="@+id/opencv_part_java_surface_view"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:visibility="gone"
opencv:camera_id="any"
opencv:show_fps="true" />
</LinearLayout>
</LinearLayout>
EDITAR native-lib.cpp para agregar funciones C++ que convertirán la imagen recibida de la cámara del Celular y la devolverán en escala de grises
- El código de native-lib.cpp debe quedar:
NOTA: En el código siguiente, los nombres de las funciones "_com_apps_jjgarsal" corresponden a la ruta del proyecto en Android y "opencvwithc" es el nombre del proyecto en minúsculas
#include <jni.h>
#include <string>
#include <opencv2/core.hpp>
// Encabezados para trabajar con funciones de procesamiento de imágenes
#include <opencv2/highgui.hpp>
#include <opencv2/imgproc.hpp>
extern "C" {
JNIEXPORT jstring
JNICALL
Java_com_apps_jjgarsal_opencvwithc_MainActivity_stringFromJNI(
JNIEnv *env,
jobject/* this */) {
std::string hello = "Hello from C++ En Opencv";
return env->NewStringUTF(hello.c_str());
}
JNIEXPORT jstring
Java_com_apps_jjgarsal_opencvwithc_MainActivity_validate(
JNIEnv * env, jobject/* this */) {
cv::Rect();
cv::Mat(); // Esta línea crea una matriz con opencv
std::string hello2 = "Hello from validate";
return env -> NewStringUTF(hello2.c_str()) ;
}
//############ Codigo para uso de cámara
using namespace std;
using namespace cv;
int toGray(Mat img, Mat& gray);
extern "C" {
JNIEXPORT jint JNICALL Java_com_apps_jjgarsal_opencvwithc_MainActivity_convertNativeGray(JNIEnv*, jobject, jlong addrRgba, jlong addrGray);
JNIEXPORT jint JNICALL Java_com_apps_jjgarsal_opencvwithc_MainActivity_convertNativeGray(JNIEnv*, jobject, jlong addrRgba, jlong addrGray) {
Mat& mRgb = *(Mat*)addrRgba;
Mat& mGray = *(Mat*)addrGray;
int conv;
jint retVal;
conv = toGray(mRgb, mGray);
retVal = (jint)conv;
return retVal;
}
}
int toGray(Mat img, Mat& gray)
{
cvtColor(img, gray, CV_RGBA2GRAY); // Assuming RGBA input
if (gray.rows == img.rows && gray.cols == img.cols)
{
return (1);
}
return(0);
}
}
EDITAR código de MainActivity.java para uso de la cámara y conversión de la imagen de entrada a escala de grises, los cambios se añadieron al código del archivo de la primera parte de este tutorial
- El código debe quedar:
/**
* Uso de Opencv con C++ en Android Studio
*
* Autor: Juan José Garza Saldaña
* Basado en los siguientes tutoriales:
*
* 1.Android Compile OpenCv Native C++ (Video de Youtube)
* https://www.youtube.com/watch?v=Vp20EdU5qjU
*
* 2. Using C++ OpenCV code with Android
* http://www.jayrambhia.com/blog/ndk-android-opencv
* https://github.com/jayrambhia/nativecodeGray (código fuente original)
*
* Notas: Con relación a los tutoriales originales
* org.opencv.android.NativeCameraView //No existe en opencv3 (versiones mas nuevas)
*
*
*/
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.TextView;
// ############# Agregado para trabajo con cámara
import org.opencv.android.BaseLoaderCallback;
import org.opencv.android.CameraBridgeViewBase;
import org.opencv.android.CameraBridgeViewBase.CvCameraViewFrame;
import org.opencv.android.CameraBridgeViewBase.CvCameraViewListener2;
import org.opencv.android.LoaderCallbackInterface;
import org.opencv.android.OpenCVLoader;
import org.opencv.core.*;
import android.util.Log;
import android.view.SurfaceView;
import android.view.WindowManager;
// #############
public class MainActivity extends AppCompatActivity implements CvCameraViewListener2 {
// ############# Agregado para trabajo con cámara
private Mat mRgba;
private Mat mGray;
private CameraBridgeViewBase mOpenCvCameraView;
private static final String TAG = "OCVSample::NDK";
// #############
// Used to load the 'native-lib' library on application startup.
static {
System.loadLibrary("native-lib"); //native-lib es el archivo C++ native-lib.cpp
System.loadLibrary("opencv_java3");
}
// ############# Agregado para cámara
/* BaseLoaderCallback instala opencvManager desde Google Play sino esta instalado y si hay Internet,
sustituye al código de static { } de líneas arriba aquí deberían ser cargadas las librerías
*/
private BaseLoaderCallback mLoaderCallback = new BaseLoaderCallback(this) {
@Override
public void onManagerConnected(int status) {
switch (status) {
case LoaderCallbackInterface.SUCCESS:
{
// System.loadLibrary("native-lib"); //native-lib es el archivo C++ native-lib.cpp
// System.loadLibrary("opencv_java3");
Log.i(TAG, "OpenCV loaded successfully");
mOpenCvCameraView.enableView();
} break;
default:
{
super.onManagerConnected(status);
} break;
}
}
};
// #############
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Example of a call to a native method
TextView tv = (TextView) findViewById(R.id.sample_text);
tv.setText(stringFromJNI());
if (!OpenCVLoader.initDebug()){
tv.setText(tv.getText() + "\n OpenCv no Funciona");
}else{
tv.setText(tv.getText() + "\n OpenCv Funciona");
tv.setText(tv.getText() + "\n" + validate());
}
// ################## Agregado para cámara
Log.i(TAG, "called onCreate");
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
mOpenCvCameraView = (CameraBridgeViewBase) findViewById(R.id.opencv_part_java_surface_view);
mOpenCvCameraView.setVisibility(SurfaceView.VISIBLE);
mOpenCvCameraView.setCvCameraViewListener(this);
// ##################
}
// ################## Agregado para cámara
@Override
public void onPause()
{
super.onPause();
if (mOpenCvCameraView != null)
mOpenCvCameraView.disableView();
}
@Override
public void onResume()
{
super.onResume();
// El tutorial original utiliza la version 2.4.8 de Opencv
// OpenCVLoader.initAsync(OpenCVLoader.OPENCV_VERSION_2_4_8, this, mLoaderCallback);
OpenCVLoader.initAsync(OpenCVLoader.OPENCV_VERSION_3_4_0, this, mLoaderCallback);
}
public void onDestroy() {
super.onDestroy();
if (mOpenCvCameraView != null)
mOpenCvCameraView.disableView();
}
public void onCameraViewStarted(int width, int height) {
mRgba = new Mat();
mGray = new Mat();
}
public void onCameraViewStopped() {
}
public Mat onCameraFrame(CvCameraViewFrame inputFrame) {
mRgba = inputFrame.rgba(); // Frame de la cámara guardada en matriz
// Envía el frame a la función de conversion convertNativeGray y obtiene la matriz
// con la imagen en escala de grises mGray
convertNativeGray(mRgba.getNativeObjAddr(), mGray.getNativeObjAddr());
return mGray;
}
// ##################
/**
* A native method that is implemented by the 'native-lib' native library,
* which is packaged with this application.
*/
public native String stringFromJNI(); //Encabezado de función en C++
public native String validate(); //Encabezado de función en C++
//Encabezado de función en C++, función para convertir imagen a escala de grises
public native int convertNativeGray(long matAddrRgba, long matAddrGray);
}
PROBAR LA APLICACIÓN
NOTA: Celulares con Android de versiones recientes requieren que al instalar la aplicación, se les configuren como activados los permisos de cámara desde la configuración del dispositivo móvil. Este código no los hábilita automáticamente.
Al ejecutar mostrará los mensajes de la PRIMERA PARTE y mostrará una visualización de la cámara, originalmente debería mostrarse en color, pero debido al procesamiento de la SEGUNDA PARTE cada frame del video se muestra en escala de grises