有读者问到 “”“从安卓模拟器路径用opencv读取图像失败”,您这个问题解决了吗?”
我刚开始将c++算法嵌入到安卓或java的时候也遇到过这个基本的问题,因为两者机制不一样,很容易搞混。
在安卓中是不能直接用opencv的imread函数的,必须以安卓自己的方式读图,然后将buffer传递给接口函数。我的算法接口用了jni封装,下面做个一简单的例子进行示例说明,希望对像我这样的安卓初学者有点帮助。
java读图并转换到字节数组中
方式一: 从res文件夹读取(传入c++接口有问题)
/*
* test_image为图像的名称, 测试图像放在app\src\main\res\drawable文件夹下
*/
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.test_image);
int height = bitmap.getHeight();
int width = bitmap.getWidth();
byte[] data = bitmap.getNinePatchChunk();
进入c++函数后,测试一下data数组是否正确,不行的话就用下面的转换一下(将Bitmap对象读到字节数组中):
ByteArrayOutputStream baos = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, baos);
byte[] datas = baos.toByteArray();
存在一个问题: 默认读入的图像格式是ARGB_8888类型,且每个通道占1个byte
方式二:从assets文件夹读取(传入c++接口测试ok, 与VS中的结果一致)
/*
* images/lena_gray.jpg , 测试图像放在app\src\main\assets\images文件夹下
* 用到的接口函数为ReadImageFromLocal
*/
package com.example.builtinalgorithm;
import androidx.appcompat.app.AppCompatActivity;
import android.content.res.AssetManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.graphics.Rect;
import android.os.Bundle;
import android.util.Log;
import android.widget.TextView;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
public class MainActivity extends AppCompatActivity {
// Used to load the 'textdetection-lib' library on application startup.
//static {
// System.loadLibrary("textdetection-lib");
//}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
String str = stringFromJNI();
AssetManager assetManager = getAssets();
String[] files = null;
try {
//files = assetManager.list("image"); //image 表示assets文件夹下的子文件夹名称
files = assetManager.list("");
} catch (IOException e) {
Log.e("tag", e.getMessage());
str += e.getMessage();
str += "\n";
}
//InputStream in = getAssets().open("file:///android_asset/frozen_east_text_detection.pb");
// Example of a call to a native method
TextView tv = findViewById(R.id.sample_text);
String assetFile = "file number is: " + Integer.toString (files.length) +" First file name is: "+ files[0] + "\n";
str += assetFile;
assetFile = "file number is: " + Integer.toString (files.length) +" Second file name is: "+ files[1] + "\n";
str += assetFile;
assetFile = "file number is: " + Integer.toString (files.length) +" Third file name is: "+ files[2] + "\n";
str += assetFile;
//str += ReadNetFromLocal(files[0]); //F:/text_detect/models/east/frozen_east_text_detection.pb "file:///android_asset/frozen_east_text_detection.pb"
//Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.lena_gray);
InputStream inputStream = null;
try {
inputStream = getResources().getAssets().open("images/lena_gray.jpg");
} catch (IOException e) {
e.printStackTrace();
str += e.getMessage();
str += "\n";
}
// 直接读取 ARGB_8888 占4个byte
//Bitmap bitmap = BitmapFactory.decodeStream(inputStream);
// 灰度图像只需要一个通道表示:转换为一个通道,占1byte,这时只有alpha通道
BitmapFactory.Options options = new BitmapFactory.Options();
options.inPreferredConfig = Bitmap.Config.ALPHA_8;
Rect rect = new Rect();
Bitmap bitmap = BitmapFactory.decodeStream(inputStream, rect, options);
// lena_gray.jpg大小为512*512,第一个像素值为163
int height = bitmap.getHeight();
int width = bitmap.getWidth();
int color = bitmap.getPixel(0,0);
int a = Color.alpha(color);
int bytes = bitmap.getByteCount();
str += Integer.toString(width) + " " + Integer.toString(height) +" " + Integer.toString(bytes) +" " + Integer.toString(a) + "\n";
// Bitmap转换为byte[]
ByteBuffer buf = ByteBuffer.allocate(bytes);
bitmap.copyPixelsToBuffer(buf);
byte[] datas = buf.array();
str += Integer.toString(datas.length) + "\n";
str += ReadImageFromLocal(datas, width, height,1);
tv.setText(str);
}
/**
* A native method that is implemented by the 'native-lib' native library,
* which is packaged with this application.
*/
public native String stringFromJNI();
public native String ReadNetFromLocal(String path);
public native String ReadImageFromLocal(byte[] imgdata, int width, int height, int channels);
下面展示一下native-cpp中的接口函数。这部分是用来测试传入的buffer是否正确。主要查看通道、pixel,然后对传入的buffer进行简单的均值方差计算,验证与c++版本下的结果是否一样即可。
#include <jni.h>
#include <string>
#include <opencv2/opencv.hpp>
using namespace cv;
using namespace std;
using namespace cv::dnn;
extern "C" JNIEXPORT jstring JNICALL
Java_com_example_builtinalgorithm_MainActivity_ReadImageFromLocal(
JNIEnv *env,
jobject /* this */, jbyteArray imgdata, jint width, jint height, jint channels) {
// jboolean iscopy;
// const char* nativeString = env->GetStringUTFChars(js, &iscopy);
// string file(nativeString);
std::string tips = "In cpp function: \n";
int ch= channels;
int cols = width;
int rows = height;
jsize len = env->GetArrayLength(imgdata);
tips += "GetArrayLength: ";
tips += to_string((int)len);
tips += "\n";
// jbyte *jbarray = (jbyte *)malloc(len * sizeof(jbyte));
// env->GetByteArrayRegion(imgdata, 0, len, jbarray);
jbyte* jbarray = env->GetByteArrayElements(imgdata,0);
unsigned char *dDate = (unsigned char*)jbarray;
try {
//Mat img = imread(file);
Mat img;
int type = CV_8UC1;
if(ch==3){
type = CV_8UC3;
Mat tmp(rows,cols,type,dDate);
cvtColor(tmp,img,COLOR_RGB2GRAY);
}
else if(ch==1){
type = CV_8UC1;
Mat tmp(rows,cols,type,dDate);
img = tmp;
}
else if(ch==4){
type = CV_8UC4;
Mat tmp(rows,cols,type,dDate);
cvtColor(tmp,img,COLOR_RGBA2GRAY);
}
else{
tips += "param:channels is error";
return env->NewStringUTF(tips.c_str());
}
tips += to_string(cols);
tips += " ";
tips += to_string(rows);
tips += " ";
int pixl = *(img.ptr<uchar>(0)+0);
tips += to_string(pixl);
tips += "\n";
Scalar a,b;
meanStdDev(img, a,b);
tips += "Load image is ok: ";
tips += to_string(a[0]);
tips += " ";
tips += to_string(b[0]);
tips += "\n";
}catch(cv::Exception e)
{
String error = e.msg;
tips += error;
tips += "Load image error. \n";
}
return env->NewStringUTF(tips.c_str());
}
关于CMakeList.txt中的opencv配置这里再重复一遍,我当前用的native-lib名称改了一下:textdetection-lib,其他出现opencv字眼的地方都是opencv的配置
# For more information about using CMake with Android Studio, read the
# documentation: https://d.android.com/studio/projects/add-native-code.html
# Sets the minimum version of CMake required to build the native library.
cmake_minimum_required(VERSION 3.4.1)
# 2019.10.31
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
set(opencv_411_dir E:/opencv-4.1.1-android-sdk/OpenCV-android-sdk/sdk/native/jni)
set(app_dir F:/android/AndroidStudioProjects/BuiltinAlgorithm/app)
include_directories(${opencv_411_dir}/include)
add_library(opencv-lib SHARED IMPORTED)
set_target_properties(opencv-lib PROPERTIES IMPORTED_LOCATION ${app_dir}/src/main/jniLibs/${ANDROID_ABI}/libopencv_java4.so)
# 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.
textdetection-lib
# Sets the library as a shared library.
SHARED
# Provides a relative path to your source file(s).
textdetection-lib.cpp)
# 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.
target_link_libraries( # Specifies the target library.
textdetection-lib
opencv-lib #2019.10.31
# Links the target library to the log library
# included in the NDK.
${log-lib})