Android Studio 导入 opencv

问题

尝试了很多不同的版本,通过implementation来添加opencv,最终都不能正常使用opencv
在这里插入图片描述
这种方法导入的opencv没有对应的java代码,古不能
在这里插入图片描述

在这里插入图片描述
即使能导入,也使用不了,就没有对应的java代码,很奇怪,网上搜索查看相关的导入基本上都是下载包到本地,本地导入opencv包的,故得出结论:android 不能通过implementation添加opencv

Android Studio 导入 opencv

https://opencv.org/releases/
推荐官网下载:我这里下载的是4.7.0的版本
在这里插入图片描述
下载完成,解压得到一下目录:
在这里插入图片描述
其中sdk文件夹是我们要导入的目标。

在进行导入前,有些准备工作。
你需要配置NDK环境和CMake,将它们勾选下载下来。
下载过程很简单,勾选要配置的环境,然后同意协议,然后下一步下一步,ok就行。
在这里插入图片描述
接下来导入opencv
在这里插入图片描述
然后你会穿越到这个界面:
在这里插入图片描述
选择你解压后的sdk文件夹:
在这里插入图片描述
在这里插入图片描述

第一次导入报错:

A problem occurred evaluating project ':opencv'.
> Plugin with id 'kotlin-android' not found.

在这里插入图片描述

解决方法:进入opencv下的build.gradle文件中,将开头的一行apply plugin: 'kotlin-android’注释掉即可。
在这里插入图片描述
最后直接sync now。

检查

检查一下,setting.grandle
在这里插入图片描述
查看是否自动包含了opencv
在这里插入图片描述
然后检查opencv下的build.gradle
在这里插入图片描述
检查一下minSdkVersion和targetSdkVersion是否与你项目的相同。记得添加

namespace "org.opencv"

最后一步,在app下的build.gradle,翻到最下边,在dependencies中添加依赖。
在这里插入图片描述

implementation(project(":opencv"))

最后,验证一下OpenCV是否正常加载。

@Override
    protected void onResume() {
        super.onResume();
        if (!OpenCVLoader.initDebug()) {
            Log.d("openCv", "OpenCv加载失败...");
        } else {
            Log.d("openCv", "OpenCv加载成功...");
        }
    }

貌似除了导入opencv文件夹外可以从.gradle文件导入opencv且能正常调用(注:实际调用的是javacv)

dependencies {
    implementation fileTree(include: ['*.jar'], dir: 'libs')
    implementation 'androidx.appcompat:appcompat:1.0.2'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
    implementation 'com.google.android.material:material:1.2.1'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'androidx.test:runner:1.2.0'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
    implementation 'androidx.recyclerview:recyclerview:1.0.0'
    implementation 'com.github.bumptech.glide:glide:3.7.0'
    implementation 'com.jelly:mango:1.1.1'
    implementation 'org.greenrobot:eventbus:3.1.1'
//    implementation 'com.android.support:design:28.0.0-rc02'
    implementation 'com.lzy.net:okgo:3.0.4'
    implementation 'com.lzy.net:okrx2:2.0.2'
    implementation 'com.lzy.net:okserver:2.0.5'
    implementation 'org.codehaus.jackson:jackson-mapper-asl:1.9.12'

    def room_version = "2.2.2"
    implementation "androidx.room:room-runtime:$room_version"
    annotationProcessor "androidx.room:room-compiler:$room_version"

    // https://mvnrepository.com/artifact/org.apache.commons/commons-math3
    implementation group: 'org.apache.commons', name: 'commons-math3', version: '3.6.1'

    implementation 'com.github.PhilJay:MPAndroidChart:v3.1.0'

    // 日志记录功能
    implementation 'com.jakewharton.timber:timber:4.7.1'

    // 不要使用 javacv-platform,而是分别指定Android版本
    implementation 'org.bytedeco:javacv:1.5.9'
    implementation 'org.bytedeco:opencv:4.7.0-1.5.9:android-arm64'
    implementation 'org.bytedeco:ffmpeg:5.1.2-1.5.9:android-arm64'
    implementation 'org.bytedeco:opencv:4.7.0-1.5.9:android-arm'
    implementation 'org.bytedeco:ffmpeg:5.1.2-1.5.9:android-arm'
    implementation 'org.bytedeco:openblas:0.3.21-1.5.9:android-arm64'

    //JavaCV
//    def javacvVersion = '1.4.2'
//    def ffmpegVersion = '4.0.1'
//    def opencvVersion = '3.4.2'
//    implementation(group: 'org.bytedeco', name: 'javacv-platform', version: javacvVersion) {
//        exclude group: 'org.bytedeco.javacpp-presets'
//    }
//    implementation group: 'org.bytedeco.javacpp-presets', name: 'ffmpeg', version: "${ffmpegVersion}-${javacvVersion}"
////    implementation group: 'org.bytedeco.javacpp-presets', name: 'ffmpeg', version: "${ffmpegVersion}-${javacvVersion}", classifier: 'android-arm' // for 'armeabi-v7a'
//    implementation group: 'org.bytedeco.javacpp-presets', name: 'ffmpeg', version: "${ffmpegVersion}-${javacvVersion}", classifier: 'android-arm64' // for 'arm64-v8a'
////    implementation group: 'org.bytedeco.javacpp-presets', name: 'opencv', version: "${opencvVersion}-${javacvVersion}"
//    implementation group: 'org.bytedeco.javacpp-presets', name: 'opencv', version: "${opencvVersion}-${javacvVersion}", classifier: 'android-arm' // for 'armeabi-v7a'
//    implementation group: 'org.bytedeco.javacpp-presets', name: 'opencv', version: "${opencvVersion}-${javacvVersion}", classifier: 'android-arm64' // for 'arm64-v8a'

//    implementation(project(":opencv"))
}
    // 不要使用 javacv-platform,而是分别指定Android版本
    implementation 'org.bytedeco:javacv:1.5.9'
    implementation 'org.bytedeco:opencv:4.7.0-1.5.9:android-arm64'
    implementation 'org.bytedeco:ffmpeg:5.1.2-1.5.9:android-arm64'
    implementation 'org.bytedeco:opencv:4.7.0-1.5.9:android-arm'
    implementation 'org.bytedeco:ffmpeg:5.1.2-1.5.9:android-arm'
    implementation 'org.bytedeco:openblas:0.3.21-1.5.9:android-arm64'

在这里插入图片描述

ContourExtractor.java
不过使用的不是原始的opencv了,而是javacv

package com.rtcce.laser.pad.yolo;

import org.bytedeco.javacpp.indexer.IntRawIndexer;
import org.bytedeco.javacv.AndroidFrameConverter;
import org.bytedeco.javacv.Frame;
import org.bytedeco.javacv.OpenCVFrameConverter;
import org.bytedeco.opencv.global.opencv_core;
import org.bytedeco.opencv.global.opencv_imgproc;
import org.bytedeco.opencv.opencv_core.Mat;
import org.bytedeco.opencv.opencv_core.MatVector;
import org.bytedeco.opencv.opencv_core.Point;
import org.bytedeco.opencv.opencv_core.Scalar;
import org.bytedeco.opencv.opencv_core.Size;

import android.graphics.Bitmap;
import android.graphics.PointF;

import java.util.ArrayList;
import java.util.List;

public class ContourExtractor {

    /**
     * 从旋转矩形区域提取轮廓点(JavaCV 版本)。
     * 返回的 Mat 为 (N x 1 x 2) 的 CV_32SC2,表示采样后的点集。
     */
    public static Mat extractContourFromRotatedRect(Mat image,
                                                    double x, double y,
                                                    double w, double h,
                                                    double angle,
                                                    int samplePoints) {
        try {
            // 1) 计算旋转矩形 4 顶点(先以原点为中心,后旋转平移)
            double angleRad = Math.toRadians(angle);
            double cosA = Math.cos(angleRad);
            double sinA = Math.sin(angleRad);
            double halfW = w / 2.0;
            double halfH = h / 2.0;

            double[][] base = new double[][] {
                {-halfW, -halfH}, {halfW, -halfH}, {halfW, halfH}, {-halfW, halfH}
            };
            Point[] translated = new Point[4];
            for (int i = 0; i < 4; i++) {
                double px = base[i][0];
                double py = base[i][1];
                double rx = px * cosA - py * sinA;
                double ry = px * sinA + py * cosA;
                translated[i] = new Point((int) Math.round(rx + x), (int) Math.round(ry + y));
            }

            // 将 4 顶点放入一个 CV_32SC2 的 Mat(4x1x2)
            Mat rectPts = new Mat(4, 1, opencv_core.CV_32SC2);
            IntRawIndexer rectIdx = rectPts.createIndexer();
            for (int i = 0; i < 4; i++) {
                rectIdx.put(i, 0, 0, (int) Math.round(translated[i].x()));
                rectIdx.put(i, 0, 1, (int) Math.round(translated[i].y()));
            }
            rectIdx.release();

            // 2) 生成掩码并二值化
            Mat mask = new Mat(image.rows(), image.cols(), opencv_core.CV_8UC1);
            opencv_core.bitwise_not(mask, mask); // 全 0
            MatVector poly = new MatVector(1);
            poly.put(0, rectPts);
            opencv_imgproc.fillPoly(mask, poly, new Scalar(255, 255, 255, 0));

            Mat gray = new Mat();
            if (image.channels() == 3) {
                opencv_imgproc.cvtColor(image, gray, opencv_imgproc.COLOR_BGR2GRAY);
            } else if (image.channels() == 4) {
                opencv_imgproc.cvtColor(image, gray, opencv_imgproc.COLOR_BGRA2GRAY);
            } else {
                gray = image.clone();
            }

            Mat binary = new Mat();
            opencv_imgproc.threshold(gray, binary, 0, 255,
                    opencv_imgproc.THRESH_BINARY + opencv_imgproc.THRESH_OTSU);
            opencv_core.bitwise_not(binary, binary);

            Mat kernel = opencv_imgproc.getStructuringElement(opencv_imgproc.MORPH_RECT, new Size(3, 3));
            opencv_imgproc.morphologyEx(binary, binary, opencv_imgproc.MORPH_CLOSE, kernel);
            opencv_imgproc.morphologyEx(binary, binary, opencv_imgproc.MORPH_OPEN, kernel);

            Mat binaryMask = new Mat(image.rows(), image.cols(), opencv_core.CV_8UC1);
            opencv_core.bitwise_and(binary, mask, binaryMask);

            Mat largeKernel = opencv_imgproc.getStructuringElement(opencv_imgproc.MORPH_RECT, new Size(15, 15));
            opencv_imgproc.morphologyEx(binaryMask, binaryMask, opencv_imgproc.MORPH_CLOSE, largeKernel);

            // 3) 查找轮廓(JavaCV 需要 MatVector)
            MatVector contours = new MatVector();
            Mat hierarchy = new Mat();
            opencv_imgproc.findContours(binaryMask, contours, hierarchy,
                    opencv_imgproc.RETR_EXTERNAL, opencv_imgproc.CHAIN_APPROX_SIMPLE);
            if (contours.size() == 0) {
                return null;
            }

            // 4) 选择最大面积轮廓
            Mat largest = contours.get(0);
            double maxArea = opencv_imgproc.contourArea(largest);
            for (long i = 1; i < contours.size(); i++) {
                Mat c = contours.get(i);
                double a = opencv_imgproc.contourArea(c);
                if (a > maxArea) {
                    maxArea = a;
                    largest = c;
                }
            }

            int n = largest.rows();
            if (n <= 0) return null;
            IntRawIndexer cIdx = largest.createIndexer();

            // 5) 均匀采样点
            int outCount = Math.min(samplePoints, n);
            Mat result = new Mat(outCount, 1, opencv_core.CV_32SC2);
            IntRawIndexer rIdx = result.createIndexer();

            // 计算累积弧长
            double[] cum = new double[n + 1];
            cum[0] = 0;
            for (int i = 0; i < n - 1; i++) {
                int x0 = cIdx.get(i, 0, 0);
                int y0 = cIdx.get(i, 0, 1);
                int x1p = cIdx.get(i + 1, 0, 0);
                int y1p = cIdx.get(i + 1, 0, 1);
                double dx = x1p - x0;
                double dy = y1p - y0;
                cum[i + 1] = cum[i] + Math.hypot(dx, dy);
            }
            cum[n] = cum[n - 1];

            if (cum[n] == 0) {
                double step = (double) n / outCount;
                for (int i = 0; i < outCount; i++) {
                    int idx = (int) Math.min(Math.round(i * step), n - 1);
                    rIdx.put(i, 0, 0, cIdx.get(idx, 0, 0));
                    rIdx.put(i, 0, 1, cIdx.get(idx, 0, 1));
                }
            } else {
                for (int i = 0; i < outCount; i++) {
                    double dist = (double) i / outCount * cum[n];
                    int idx = 0;
                    for (int j = 0; j < n; j++) {
                        if (dist <= cum[j + 1]) { idx = j; break; }
                    }
                    idx = Math.min(idx, n - 2);
                    double segLen = cum[idx + 1] - cum[idx];
                    double t = segLen > 0 ? (dist - cum[idx]) / segLen : 0.0;
                    int x0 = cIdx.get(idx, 0, 0);
                    int y0 = cIdx.get(idx, 0, 1);
                    int x1p = cIdx.get(idx + 1, 0, 0);
                    int y1p = cIdx.get(idx + 1, 0, 1);
                    int xi = (int) Math.round((1 - t) * x0 + t * x1p);
                    int yi = (int) Math.round((1 - t) * y0 + t * y1p);
                    rIdx.put(i, 0, 0, xi);
                    rIdx.put(i, 0, 1, yi);
                }
            }

            cIdx.release();
            rIdx.release();
            return result;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 从 Android Bitmap 中提取 line 的轮廓点(JavaCV 版本)。
     */
    public static List<PointF> extractContourFromBitmap(Bitmap bitmap,
                                                        double centerX, double centerY,
                                                        double width, double height,
                                                        double angle, int samplePoints) {
        try {
            if (bitmap == null || bitmap.isRecycled()) {
                return null;
            }
            AndroidFrameConverter bitmapConverter = new AndroidFrameConverter();
            OpenCVFrameConverter.ToMat toMat = new OpenCVFrameConverter.ToMat();
            Frame frame = bitmapConverter.convert(bitmap);
            if (frame == null) return null;
            Mat image = toMat.convert(frame);
            if (image == null || image.empty()) {
                return null;
            }

            Mat contourMat = extractContourFromRotatedRect(image, centerX, centerY, width, height, angle, samplePoints);
            if (contourMat == null || contourMat.empty()) {
                return null;
            }

            List<PointF> out = new ArrayList<>();
            IntRawIndexer idx = contourMat.createIndexer();
            for (int i = 0; i < contourMat.rows(); i++) {
                int x = idx.get(i, 0, 0);
                int y = idx.get(i, 0, 1);
                out.add(new PointF((float) x, (float) y));
            }
            idx.release();
            return out;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    public static List<PointF> extractContourFromBitmap(Bitmap bitmap,
                                                        double centerX, double centerY,
                                                        double width, double height,
                                                        double angle) {
        return extractContourFromBitmap(bitmap, centerX, centerY, width, height, angle, 200);
    }
}

总之这样也可以导入且不会报错。

参考文章:
Android添加OpenCV支持,一步一步添加。
使用 OpenCV 进行 Android 开发
Android Studio(新版本) 配置OpenCV库

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值