使用 OpenCV for Android 进行图像特征检测

磐创AI

    android 开发人员,可能熟悉使用activities, fragments, intents以及最重要的一系列开源依赖库。但是,注入需要本机功能的依赖关系(如计算机视觉框架)并不像在 gradle 文件中直接添加实现语句那样简单!
    今天,将专注于使用 OpenCV 库,将其作为依赖项注入到你的 android 应用程序中。
    那么,我们将从这次讨论中得到什么?我们将能够创建一个 android 应用程序并执行所需的步骤来集成 OpenCV。
    此外,我们将完成一个基于SIFT技术的图像特征检测算法。这将是你考虑构建自己的 SDK 的良好起点。
    什么是SIFT?
    SIFT 代表尺度不变傅立叶变换(Scale Invariant Fourier Transform)。检测器用于查找图像上的兴趣点。它使我们能够识别图像中的局部特征。
    SIFT 的优势在于,即使我们大幅缩放图像,它也能正常工作,因为它将图像数据转换为尺度不变坐标。SIFT 使用“关键点”来表示图像中缩放和旋转不变的局部特征。这是我们将其用于各种应用程序(如图像匹配、对象检测、场景检测等)的基准。
    为了识别关键点,该算法为你完成:
    第 1 步:形成尺度空间——这一步确保特征与尺度无关。
    第 2 步:关键点定位——这一步有助于识别合适的特征/关键点。
    第 3 步:方向对齐——这一步确保关键点是旋转后不变的。
    第 4 步:关键点描述符——这是为每个关键点创建描述符的最后一步。
    从这里,我们可以使用关键点和描述符来进行特征匹配。
    现在让我们设置android项目,
    打开Android Studio->New Project->Empty Activity
    
    下载 OpenCV 4.5.1
    提取文件夹,然后将 java 文件夹重命名为 OpenCVLibrary451
    然后使用 File->New->Import Module 并选择文件夹
    
    单击完成。然后,你必须看到该库已添加到你的项目中。
    
    点击 File->Project Structure->Dependencies 并选择 app.
    单击添加依赖项,然后选择 OpenCVLibrary451
    
    
    确保选中JDK 11,如果没有,请转到 gradle 设置并将目标版本更改为1.8。
    
    
    我们只需要再添加 JNI 库,以便调用 SIFT OpenCV 本机函数。将以下内容粘贴到应用程序的构建 gradle 文件中。android下defaultConfig下面
    sourceSets{
          main {
             jniLibs.srcDirs = ['libs']
          }
      }
    然后将几个文件复制粘贴到你在开始时提取的 opencv 文件夹 [from /OpenCV-android-sdk/sdk/native/libs] 下。转到项目的 app 文件夹,创建一个名为 libs 的文件夹并粘贴文件。
    
    同样,在应用程序的主文件夹中创建一个名为 cpp 的文件夹,然后粘贴 /OpenCV-android-sdk/sdk/libcxx_helper 中的文件。你之前提取的那个。
    
    在 android 下 app 的 build gradle 文件中粘贴以下内容
    externalNativeBuild {
      cmake {
          path file('src/main/cpp/CMakeLists.txt')
          version '3.18.1'
      }
    }
    同步 grade 文件。如果一切顺利,你将看到应用程序的构建。
    
    要测试应用程序,请将 bmp 粘贴到可绘制对象中。我在这里使用了 used test.bmp。
    收集 bmp 文件后,将以下内容粘贴到 resources->layout->activity_main.xml
    <?xml version="1.0" encoding="utf-8"?>
    <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
      xmlns:app="http://schemas.android.com/apk/res-auto"
      xmlns:tools="http://schemas.android.com/tools"
      android:layout_width="match_parent"
      android:layout_height="match_parent"
      tools:context=".MainActivity">
      <TextView
          android:id="@+id/sample_text"
          android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          android:text="Hello OpenCV Android!!!"
          app:layout_constraintBottom_toBottomOf="parent"
          app:layout_constraintLeft_toLeftOf="parent"
          app:layout_constraintRight_toRightOf="parent"
          app:layout_constraintTop_toTopOf="parent" />
      <ImageView
          android:id="@+id/imageView"
          android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          app:layout_constraintBottom_toTopOf="@+id/sample_text"
          app:layout_constraintEnd_toEndOf="parent"
          app:layout_constraintHorizontal_bias="0.498"
          app:layout_constraintStart_toStartOf="parent"
          app:layout_constraintTop_toTopOf="parent"
          app:layout_constraintVertical_bias="0.641"
          app:srcCompat="@drawable/ic_launcher_background" />
    </androidx.constraintlayout.widget.ConstraintLayout>
    然后,将以下代码粘贴到 ActivityMain.kt 中
    package com.augray.siftandroid
    import android.graphics.Bitmap
    import android.graphics.BitmapFactory
    import android.os.Bundle
    import android.view.View
    import android.widget.ImageView
    import android.widget.TextView
    import androidx.appcompat.app.AppCompatActivity
    import org.opencv.android.Utils
    import org.opencv.core.Mat
    import org.opencv.core.MatOfKeyPoint
    import org.opencv.features2d.Features2d
    import org.opencv.features2d.SIFT
    import org.opencv.imgproc.Imgproc
    class MainActivity : AppCompatActivity() {
      companion object {
          // Used to load the 'native-lib' library on application startup.
          init {
              System.loadLibrary("native-lib")
              System.loadLibrary("opencv_java4")
          }
      }
      private var imageView: ImageView? = null
      // make bitmap from image resource
      private var inputImage: Bitmap? = null
      private val sift = SIFT.create()
      override fun onCreate(savedInstanceState: Bundle?) {
          super.onCreate(savedInstanceState)
          setContentView(R.layout.activity_main)
          inputImage = BitmapFactory.decodeResource(resources, R.drawable.test)
          imageView = findViewById<View>(R.id.imageView) as ImageView
          detectAndDrawKeypoints()
      }
      fun detectAndDrawKeypoints() {
          val rgba = Mat()
          Utils.bitmapToMat(inputImage, rgba)
          val keyPoints = MatOfKeyPoint()
          Imgproc.cvtColor(rgba, rgba, Imgproc.COLOR_RGBA2GRAY)
          sift.detect(rgba, keyPoints)
          Features2d.drawKeypoints(rgba, keyPoints, rgba)
          Utils.matToBitmap(rgba, inputImage)
          imageView!!.setImageBitmap(inputImage)
      }
    }
    让我们看一下上面的一些代码,以便更好地理解
    System.loadLibrary("native-lib")
    System.loadLibrary("opencv_java4")
    当 cmake 为我们构建所有类并准备就绪时,我们仍然没有 SIFT 模块,很遗憾,它移到了新版本 OpenCV 中的其他库中。
    函数 detectAndDrawKeypoints() 获取位图并将其转换为图像数组(矩阵/多维数组),并使用 SIFT 模块检测关键点。如果图像具有良好的对比度、细节和较少重复的图案,检测将产生尽可能多的关键点。
    构建并运行应用程序
    我们刚刚检测到图像中的特征。
    我们现在可以扩展它来拍摄另一张图像,获取它的关键点并最终匹配它们以获得相似性。