imread 未从 Linux Java Android 上读取现有图像

imread not reading an existing image on Java Android from Linux

package com.example.cppinandroid;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.widget.TextView;
import org.opencv.core.Mat;
import java.io.File;
import static org.opencv.imgcodecs.Imgcodecs.CV_LOAD_IMAGE_GRAYSCALE;
import org.opencv.imgcodecs.Imgcodecs;

public class MainActivity extends AppCompatActivity
{
    @Override
    protected void onCreate(Bundle savedInstanceState) 
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        TextView tv = findViewById( R.id.sample_text );

        Mat image;
        image = Imgcodecs.imread( "/home/<myName>/a.png", CV_LOAD_IMAGE_GRAYSCALE);
        if ( image.size().height > 0 )
        {
            tv.setText( "No datfa in image!");
        }
        else
        {
            tv.setText( "xxff " + image.size().height);
        }
    }
}

我没有使用任何可绘制或外部媒体。图像存在于主文件夹中,可以通过提供完全相同的路径由普通的 opencv c++ 程序打开。

。好的。这就是全部Java。

当我执行它时,它总是进入 else 语句并将高度显示为 0.0

我已经删除了多余的代码。

在 Linux 上的这个程序中,从主文件夹中读取普通 png 的方法是什么?

问题下方评论中提到的第一个问题是必须加载实现图片加载的原生库。这可以通过以下代码完成:

    static {
        // TODO: use OpenCVLoader.initAsync for a real application
        if (!OpenCVLoader.initDebug()) {
            Log.d(TAG, "Failed to initialize OpenCV.");
        }
    }

在实际应用程序中,您将使用 initAsync 函数,这样库的加载不会阻塞主线程。在一个简单的例子中,这无关紧要。

另一个问题是,如果您想访问任意目录中的文件,Android 上的文件 IO 需要权限。该权限必须在您的清单文件中声明。这可以通过在清单文件中的 application 标记上方添加以下两行来完成。

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

必须在运行时请求这些权限。要检查权限是否已被授予,可以使用以下代码:

if (ContextCompat.checkSelfPermission(this,
    android.Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
    // do something with the permission
}

如果没有权限,可以通过以下方式申请:

ActivityCompat.requestPermissions(this, new String[]{
    android.Manifest.permission.WRITE_EXTERNAL_STORAGE}, RW_PERMISSION_REQUEST_CODE);

注意:我们只请求写入权限,因为它们是分组的,如果用户授予写入权限,我们也会自动获得读取权限。

要处理结果,activity class 中的 onRequestPermissionsResult 回调应该被覆盖,如下面的完整代码示例所示。因为权限系统相当复杂,请查看 official documentation. For info on requesting permissions look here.

最后要使加载工作文件路径必须正确。用户可访问的内存位置取决于 phone 制造商,因此最好使用 Android 提供的系统方法来找到正确的路径,例如 getExternalStorageDirectory()。有关各种存储位置的更多信息,请参见 here.

这里是完整代码:


import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE;
import static android.os.Environment.getExternalStorageDirectory;

public class MainActivity extends AppCompatActivity {

    private static String TAG = "MainActivity";
    private static final int RW_PERMISSION_REQUEST_CODE = 123;

    static {
        // TODO: use OpenCVLoader.initAsync for a real application
        if (!OpenCVLoader.initDebug()) {
            Log.d(TAG, "Failed to initialize OpenCV.");
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        if (ContextCompat.checkSelfPermission(
                this, WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
            permissionDenied();
        } else {
            permissionGranted();
        }
    }

    private void permissionDenied() {
        if (ActivityCompat.shouldShowRequestPermissionRationale(this, WRITE_EXTERNAL_STORAGE)) {
            new AlertDialog.Builder(this)
                    .setTitle("Read/Write permission required to read an image file.")
                    .setCancelable(false)
                    .setPositiveButton("Grant", (dialog, which) ->
                            ActivityCompat.requestPermissions(this, new String[]{
                                    WRITE_EXTERNAL_STORAGE}, RW_PERMISSION_REQUEST_CODE))
                    .setNegativeButton("Deny", (dialog, which) -> {
                        Toast.makeText(this,
                                "App cannot work without permission.", Toast.LENGTH_LONG).show();
                        this.finish();
                    })
                    .create()
                    .show();
        } else {
            ActivityCompat.requestPermissions(
                    this, new String[]{WRITE_EXTERNAL_STORAGE}, RW_PERMISSION_REQUEST_CODE);
        }
    }

    private void permissionGranted() {
        String path = getExternalStorageDirectory().getAbsolutePath() + "/a.png";

        Mat image = Imgcodecs.imread(path, Imgcodecs.IMREAD_GRAYSCALE);
        if (image.empty()) {
            Toast.makeText(this, "Failed image", Toast.LENGTH_LONG).show();
        } else {
            Size size = image.size();
            Toast.makeText(this, "Loaded image " + size.height, Toast.LENGTH_LONG).show();

            // the following code is only necessary to display the image in an ImageView
            ImageView iv = findViewById(R.id.imageView);
            Mat tmp = new Mat((int) size.height, (int) size.width, CvType.CV_8U, new Scalar(4));
            try {
                Imgproc.cvtColor(image, tmp, Imgproc.COLOR_GRAY2RGBA, 4);
                Bitmap bmp = Bitmap.createBitmap(tmp.cols(), tmp.rows(), Bitmap.Config.ARGB_8888);
                Utils.matToBitmap(tmp, bmp);
                iv.setImageBitmap(bmp);
            } catch (CvException e) {
                Log.d(TAG, e.getMessage());
                Toast.makeText(this, "Couldn't convert image.", Toast.LENGTH_LONG).show();
            }
        }
    }

    @Override
    public void onRequestPermissionsResult(
            int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        if (requestCode == RW_PERMISSION_REQUEST_CODE) {
            if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                permissionGranted();
            } else {
                permissionDenied();
            }
        } else {
            super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        }
    }
}

要使此代码正常工作,请将 ID 为 imageViewImageView 添加到您的 activity_main.xml 布局文件中。

所有 Android 设备或​​模拟器都无法访问外部存储,例如您的 Linux 存储分区,它们可以访问其内部存储或 SD 卡。在模拟器的情况下,它们的内部存储是使用不易读取的特定格式的文件来模拟的。在启用了开发人员选项的模拟器或设备中,可以使用 Android SDK platform-tools 文件夹中的 adb 命令将文件传输到其中:

adb push file.jpg /sdcard/file.jpg

之后,您需要更改您在代码中使用的文件的路径以匹配并启用对 READ_EXTERNAL_STORAGE 的权限(这里的外部意味着您的 运行 应用程序外部,但仍位于设备内部)。

Someone here told me that native C++ NDK will not be able to read Linux paths. Alright. Here it is all Java.

查看您的问题和答案,首先这是同一个问题,试图访问不属于 device/emulator 内部存储的文件。然而答案并不完全正确,只要授予应用程序权限,C/C++代码就可以访问内部存储的文件和目录。我建议您首先尝试使用 Java 解决问题,然后切换回您在其他问题中使用的代码,但路径已更正。使用 Java,您将使用 OpenCV API 的 Java 包装器,因此您需要调用 OpenCVLoader.initDebug() 来加载包装器库。使用纯 NDK 时,您只需要加载已编译的库(System.loadLibrary(<libname>) 您使用本机 C/C++ 代码创建的。

查看此示例代码,也许对您有所帮助,我测试了此代码并为我工作,我可以获得任何图像的宽度和高度。

1) 首先您需要将 OpenCVLibrary 导入到您的项目中:请参阅此 Link - Link

2) 那么您需要为您的应用程序设置 READ_EXTERNAL_STORAGE 权限: 请在您的 AndroidManifest.xml:

上添加此命令
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

3) 你需要文件选择器来获取我使用的特定文件 ru.bartwell:exfilepicker: Link

implementation 'ru.bartwell:exfilepicker:2.1'

4) 最后,您只需将这个简单的代码添加到您的 MainActivity:

    private static final int READ_STORAGE_PERMISSION_REQUEST_CODE = 1;
    private static final int EX_FILE_PICKER_RESULT = 0;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        initLoadOpenCV();

        if (!checkPermissionForReadExtertalStorage()) {
            requestPermissionForReadExtertalStorage();
        }

    }

    private void initLoadOpenCV() {
        boolean isDebug = OpenCVLoader.initDebug();
        if (isDebug) {
            Log.i("init Opencv", "init openCV success!!");
        } else {
            Log.e("init Opencv", "init openCV failure!!");
        }
    }

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (requestCode == EX_FILE_PICKER_RESULT) {
            ExFilePickerResult result = ExFilePickerResult.getFromIntent(data);
            if (result != null && result.getCount() > 0) {
                // Here is object contains selected files names and path
                Log.i("folderLocation", result.getPath() + result.getNames().get(0));

                Mat srcMat1 = Imgcodecs.imread(result.getPath() + result.getNames().get(0));
                if (srcMat1.empty()) {
                    return;
                }

                int width = srcMat1.width();
                int height = srcMat1.height();
                int type = srcMat1.type();
                Log.i("width", srcMat1.width() + "");
                Log.i("height", srcMat1.height() + "");
                Log.i("type", srcMat1.type() + "");

            }
        }
    }

    public boolean checkPermissionForReadExtertalStorage() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            int result = getApplicationContext().checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE);
            return result == PackageManager.PERMISSION_GRANTED;
        }
        return false;
    }

    public void requestPermissionForReadExtertalStorage() {
        ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},
                READ_STORAGE_PERMISSION_REQUEST_CODE);
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
        switch (requestCode) {
            case READ_STORAGE_PERMISSION_REQUEST_CODE:
                if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    Log.e("value", "Permission Granted, Now you can use local drive .");

                    ExFilePicker exFilePicker = new ExFilePicker();
                    exFilePicker.start(this, EX_FILE_PICKER_RESULT);
                } else {
                    Log.e("value", "Permission Denied, You cannot use local drive .");
                    requestPermissionForReadExtertalStorage();
                }
                break;
        }
    }

当您使用 Imgcodecs.imread(...) 时,它会读取您机器上的路径,运行您的应用程序。

因此,如果您 运行 Java Application,它会在您计算机内的 JVM 上 运行,这意味着它读取 path 就像 ~/home/... 在您的计算机上并且该路径存在,因此它可以得到一些东西。

但是,Android 应用程序将 运行 在 Android 设备内的 DVM 上,这意味着当您读取 ~/home/.. 时,它将在 Android 上采用该路径] 设备,但它不存在于 Android 台设备上。所以你什么也得不到。

最好的做法是,您应该像上面有些人建议的那样,将 Imgcodecs.imread(...)External Storage Path 一起使用。

有时候,你也许可以在/mtn/..,SD卡的路径上使用Imgcodecs.imread(...),但它并不完全正确。

在您继续阅读之前,我想澄清一些事情

  1. 从问题中不清楚代码在哪里运行?用户似乎是 运行 这个 android 虚拟机或模拟器上的应用程序。

1.1 - 从问题来看,她想要打开的文件似乎在 Linux 机器的主目录中,而不是在模拟器的存储或 Android 设备上 - 在这种情况下请注意,模拟器上的 android 应用程序 运行 无法从您的计算机访问文件。

---- so if you were trying to access file on your Linux pc from within android emulator or vm, please note that it is not possible. Instead copy and put the file in the android emulator or device on which your app will be running.

请澄清问题并让我们知道您是否在模拟器存储(或 android 设备)上有文件,或者它在您的电脑上并且在模拟器上代码 运行。

  1. 如果您在模拟器或 android 设备上有文件,请确保您在清单中声明了正确的权限,并且在尝试读取图像之前您还请求了用户读取存储的权限。

更新

感谢您在评论中的回复。

如何把文件放到模拟器上?

To add a file to the emulated device, drag the file onto the emulator screen. The file is placed in the /sdcard/Download/ directory. You can view the file from Android Studio using the Device File Explorer, or find it from the device using the Downloads or Files app, depending on the device version

来源https://developer.android.com/studio/run/emulator

对于权限相关的内容,您可以参考官方网站上易于理解的文档 - https://developer.android.com/training/permissions/requesting

或查看此问题 -

您还可以查看 --

折旧单

请检查


为避免处理权限和外部目录,您还可以使用 android studio -- 探索设备存储将文件传输到应用的内部存储。


更新 2

请参考这个答案 -

If you want to access them in runtime and not push the files there you have to have a public IP on the internet in which you can access and expose the files on HTTP with something like Apache Tomcat or FTP and then access that from the emulator.

You can also access it locally without exposing it to the whole internet, by accessing from the Android emulator to the "localhost" IP. You'll have to search which IP this is.

所以您的用例需要从您电脑上的位置访问文件,您可以使用 tomcat 之类的东西来创建本地 http 服务器。