有讀者問到 “”“從安卓模擬器路徑用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})