求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-detection、edge-detection,熟悉Mat的创建、释放,以及相机帧的处理流程;
- 调试时可以把中间处理后的图像(比如灰度图、Canny边缘图)返回显示,方便观察每一步的效果;
- 先实现简单功能,比如“相机预览+灰度化显示”,确保每一步都能正常运行,再逐步叠加复杂逻辑。
内容的提问来源于stack exchange,提问作者Carl Est




