You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

求Android Studio中OpenCV新教程:用于毕设纸币检测APP开发

毕业设计纸币检测APP:OpenCV应用卡点求助

我正在开发用于毕业设计的纸币检测APP,该应用需通过摄像头或图片识别纸币并判定其面额。我已完成部分开发,但因无OpenCV使用经验且找不到合适的教程,目前卡在OpenCV的应用环节。

已完成的代码

activity_convert.xml

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.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=".Convert">

    <org.opencv.android.JavaCameraView
        android:id="@+id/cView"
        android:layout_width="343dp"
        android:layout_height="303dp"
        android:layout_marginBottom="8dp"
        android:layout_marginEnd="8dp"
        android:layout_marginStart="8dp"
        android:layout_marginTop="4dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.52"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.043" />

</android.support.constraint.ConstraintLayout>

Convert.java

package com.example.carlo.thesis;

import android.hardware.Camera;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.SurfaceView;

import org.opencv.android.BaseLoaderCallback;
import org.opencv.android.CameraBridgeViewBase;
import org.opencv.android.JavaCameraView;
import org.opencv.android.LoaderCallbackInterface;
import org.opencv.android.OpenCVLoader;
import org.opencv.core.Core;
import org.opencv.core.CvType;
import org.opencv.core.Mat;
import org.opencv.core.MatOfPoint;
import org.opencv.core.Scalar;
import org.opencv.imgproc.Imgproc;

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

public class Convert extends AppCompatActivity implements CameraBridgeViewBase.CvCameraViewListener2 {
    private static String TAG = "Convert";
    JavaCameraView jview;
    Mat mRgba, imgGray, imgCanny, mHierarchy;
    Scalar CONTOUR_COLOR = new Scalar(255,0,0,255);

    static {
        if(OpenCVLoader.initDebug()){
            Log.i(TAG, "OpenCV loaded.");
        }else{
            Log.i(TAG, "OpenCV not loaded.");
        }
    }

    BaseLoaderCallback mLoaderCallBack = new BaseLoaderCallback(this) {
        @Override
        public void onManagerConnected(int status) {
            switch (status){
                case BaseLoaderCallback.SUCCESS:{
                    jview.enableView();
                    break;
                }
                default:{
                    super.onManagerConnected(status);
                    break;
                }
            }
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_convert);
        jview = findViewById(R.id.cView);
        jview.setVisibility(SurfaceView.VISIBLE);
        jview.setCvCameraViewListener(this);
    }

    @Override
    protected void onPause(){
        super.onPause();
        if(jview!=null){
            jview.disableView();
        }
    }

    @Override
    protected void onDestroy(){
        super.onDestroy();
        if(jview!=null){
            jview.disableView();
        }
    }

    @Override
    protected void onResume(){
        super.onResume();
        if (OpenCVLoader.initDebug()){
            Log.i(TAG, "OpenCV loaded");
            mLoaderCallBack.onManagerConnected(LoaderCallbackInterface.SUCCESS);
        }else{
            Log.i(TAG, "OpenCV not loaded");
            OpenCVLoader.initAsync(OpenCVLoader.OPENCV_VERSION_2_4_9, this, mLoaderCallBack);
        }
    }

    @Override
    public void onCameraViewStarted(int width, int height) {
        mHierarchy = new Mat();
        mRgba = new Mat(height, width, CvType.CV_8UC4);
        imgGray = new Mat(height, width, CvType.CV_8UC1);
        imgCanny = new Mat(height, width, CvType.CV_8UC1);
    }

    @Override
    public void onCameraViewStopped() {
        mRgba.release();
    }

    @Override
    public Mat onCameraFrame(CameraBridgeViewBase.CvCameraViewFrame inputFrame) {
        // List<MatOfPoint> contours = new ArrayList<MatOfPoint>();
        mRgba = inputFrame.rgba();
        final Mat dst = new Mat(mRgba.rows(), mRgba.cols(), mRgba.type());
        mRgba.copyTo(dst);

        Imgproc.cvtColor(dst, dst, Imgproc.COLOR_BGR2GRAY);
        final List<MatOfPoint> points = new ArrayList<>();
        final Mat hierarchy = new Mat();

        Imgproc.findContours(dst, points, hierarchy, Imgproc.RETR_TREE, Imgproc.CHAIN_APPROX_SIMPLE);
        Imgproc.cvtColor(dst, dst, Imgproc.COLOR_GRAY2BGR);

        /* Imgproc.Canny(mRgba, imgCanny, 50, 100);
        //Imgproc.cvtColor(imgCanny, imgGray, Imgproc.COLOR_RGB2GRAY);
        Imgproc.findContours(imgCanny, contours, mHierarchy, Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_SIMPLE);
        Imgproc.drawContours(imgCanny, contours, -1, CONTOUR_COLOR);
        //Imgproc.findContours(mRgba, contours, mHierarchy, Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_SIMPLE);
        return contours.get(1);
        //return imgCanny;*/

        Mat mRgbaT = mRgba.t();
        Core.flip(mRgba.t(), m

针对性解决建议

首先我注意到你的Convert.java代码最后一行没写完(Core.flip(mRgba.t(), m),先把这部分补全,保证相机预览能正常运行,这是后续开发的基础。接下来针对纸币检测的核心流程,给你一步步拆解:

1. 先把OpenCV相机预览调通

补全onCameraFrame方法的末尾代码,解决Android相机预览的镜像/旋转问题:

// 补全图像翻转代码
Core.flip(mRgba.t(), mRgbaT, 1);
Imgproc.resize(mRgbaT, mRgba, mRgba.size());
// 返回处理后的图像显示
return mRgba;

2. 优化图像预处理流程

当前你直接在灰度图上找轮廓,容易受噪声干扰,建议调整预处理顺序,增加去噪步骤:

@Override
public Mat onCameraFrame(CameraBridgeViewBase.CvCameraViewFrame inputFrame) {
    mRgba = inputFrame.rgba();
    Mat gray = new Mat();
    // 1. RGB转灰度
    Imgproc.cvtColor(mRgba, gray, Imgproc.COLOR_RGBA2GRAY);
    // 2. 高斯模糊去噪(减少边缘检测的干扰,非常关键)
    Imgproc.GaussianBlur(gray, gray, new org.opencv.core.Size(5,5), 0);
    // 3. Canny边缘检测(可以根据实际场景调整阈值)
    Imgproc.Canny(gray, imgCanny, 50, 150);
    // 4. 查找最外层轮廓(减少冗余轮廓)
    List<MatOfPoint> contours = new ArrayList<>();
    Mat hierarchy = new Mat();
    Imgproc.findContours(imgCanny, contours, hierarchy, Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_SIMPLE);

    // 后续轮廓筛选逻辑...

    // 释放临时Mat,避免内存泄漏
    gray.release();
    hierarchy.release();
    
    // 补全图像翻转后返回
    Mat mRgbaT = mRgba.t();
    Core.flip(mRgbaT, mRgbaT, 1);
    Imgproc.resize(mRgbaT, mRgba, mRgba.size());
    return mRgba;
}

3. 筛选纸币轮廓

找到轮廓后,需要筛选出符合纸币形状的轮廓(通常是近似矩形,且长宽比符合目标纸币的比例):

// 在查找轮廓后添加这段代码
for (MatOfPoint contour : contours) {
    double area = Imgproc.contourArea(contour);
    // 过滤太小的轮廓(阈值根据你的相机分辨率调整)
    if (area < 20000) continue;

    // 把轮廓转成浮点型,用于多边形近似
    MatOfPoint2f contour2f = new MatOfPoint2f(contour.toArray());
    // 计算轮廓周长,epsilon设为周长的2%,用于简化轮廓
    double epsilon = 0.02 * Imgproc.arcLength(contour2f, true);
    MatOfPoint2f approx = new MatOfPoint2f();
    Imgproc.approxPolyDP(contour2f, approx, epsilon, true);

    // 判断是否是4个顶点的矩形(纸币的近似形状)
    if (approx.total() == 4) {
        // 绘制轮廓,方便调试观察
        Imgproc.drawContours(mRgba, Collections.singletonList(new MatOfPoint(approx.toArray())), -1, CONTOUR_COLOR, 3);
        // 提取纸币的ROI(感兴趣区域)
        Rect rect = Imgproc.boundingRect(contour);
        Mat paperRoi = mRgba.submat(rect);
        // 这里可以对ROI进行面额识别处理
        // ...
        paperRoi.release(); // 释放ROI内存,避免泄漏
    }
}

4. 面额识别的可行方向

拿到纸币ROI后,你可以从这几个方向入手实现面额识别:

  • 尺寸识别:根据纸币的像素长宽比(注意固定拍摄距离,或加入校准物),不同面额纸币的长宽比是固定的;
  • 模板匹配:提前准备不同面额纸币的模板图,用Imgproc.matchTemplate在ROI中匹配;
  • 颜色特征:提取ROI的主色调,比如某些面额的纸币有独特的颜色标识;
  • 数字识别:用OCR工具(比如Tesseract)识别纸币上的面额数字,需先定位数字区域。

5. OpenCV入门小技巧

  • 先从OpenCV Android官方示例项目入手,比如face-detectionedge-detection,熟悉Mat的创建、释放,以及相机帧的处理流程;
  • 调试时可以把中间处理后的图像(比如灰度图、Canny边缘图)返回显示,方便观察每一步的效果;
  • 先实现简单功能,比如“相机预览+灰度化显示”,确保每一步都能正常运行,再逐步叠加复杂逻辑。

内容的提问来源于stack exchange,提问作者Carl Est

火山引擎 最新活动