本帖最后由 xble 于 2017-3-14 09:34 编辑 一)项目概述 汽车已经成为很多人生活中的必需品,成为人们的代步工具,随之也引发了很多管理问题。车牌作为汽车的唯一身份证,其重要性不言而喻,车牌自动识别需求应运而生,它相对人工管理有很大优点:1.可以不间断的连续工作;2.准确、自动、长时间的保存车牌信息。当然也有很多缺点:1,识别成功率不高;2.位置固定,不如人工灵活。 希望可以通过这个项目让我学习到一下知识:1)MTK X20开发平台相关知识;2)学习USB Camera模块相关知识;3)学习Opencv的概念和车牌识别的原理,流程。应用到的技术主要有C/C++编程,Android开发, Opencv跨平台计算机视觉库,人工智能的相关知识,当然由于水平和时间限制我这里只是简单接触一下,在前人的基础大概了解其大概原理和实现过程。 二)硬件设计 1.X20开发板 2.USB Camera 3.HDMI转接线,DIV显示器,USB鼠标,USB下载线。 三)软件调试 1)USB Camera模块加载和抓取车牌图片 由于USB Camera模块无法固定,导致抓取图片的质量非常差,所以没有实时解析Camera拍摄图形,而是保存成图片后在进行识别。其实现参考前面的帖子,连接为: http://bbs.elecfans.com/forum.ph ... &tid=1108917&extra= 2)车牌识别原理,过程 车牌识别原理与我们前面的实现过的简化版字符数字识别全过程原理一致,只是需要添加许多额外的步骤来提高识别准确度。参考连接: http://bbs.elecfans.com/forum.ph ... &tid=1112867&extra= 我们将车牌识别实现分为两步:1)SVM训练数据准备;2)车牌定位,识别。从一定意义上来说SVM数据训练也是车牌定位识别的过程,只是我们会人工干预,确保这个过程的正确性。 车牌定位主要处理步骤:1)高斯模糊Sobel算子,闭操作;2)处理图形旋转角度;3)对比SVM数据初步判断图块是否是车牌。 字符识别主要处理步骤:1)字符分割(阀值操作,取轮廓);2)通过ANN模型进行字符识别。 如果想要产品化必须深入了解图形学的相关知识,原理,分析各种图形算法来提高识别准确性,另外准备大量的训练好的SVM和ANN数据也是提高准确性的重要方法。 3)测试车牌图片准备。 简单方法:从网上搜索一些车牌图片,使用附件里面提供的APK,准确度非常高。 Camera采集方法:附件里面的video.avi文件是我们使用USB camera拍摄的一段视频,可以在这里抓取一些车牌图片来识别,可惜这种方法的识别正确率实在不高。 将这些图片都push到/storage/emulated/0文件夹下。 4)Android+Studio分析上步所得图片,识别,显示结果 Android界面:edit输入框中的文件名加上前面的文件路径(/storage/emulated/0),显示按钮,识别按钮。代码如下: - package com.example.gzh.myplaterecognizer;
- import android.Manifest;
- import android.content.DialogInterface;
- import android.content.pm.PackageManager;
- import android.graphics.Bitmap;
- import android.graphics.BitmapFactory;
- import android.os.Environment;
- import android.support.annotation.NonNull;
- import android.support.v4.app.ActivityCompat;
- import android.support.v4.content.ContextCompat;
- import android.support.v7.app.AlertDialog;
- import android.support.v7.app.AppCompatActivity;
- import android.os.Bundle;
- import android.util.Log;
- import android.view.View;
- import android.widget.EditText;
- import android.widget.ImageView;
- import android.widget.TextView;
- import android.widget.Toast;
- import org.opencv.android.Utils;
- import org.opencv.core.Mat;
- import java.io.File;
- import java.io.FileNotFoundException;
- import java.io.IOException;
- import static org.opencv.android.OpenCVLoader.initDebug;
- import static org.opencv.imgproc.Imgproc.COLOR_RGB2GRAY;
- import static org.opencv.imgproc.Imgproc.cvtColor;
- public class MainActivity extends AppCompatActivity {
- private static final int MY_PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE = 1;
- private boolean mExternalStorageAvailable = false;
- private boolean mExternalStorageWriteable = false;
- private static final int CAMERA_REQUEST_CODE = 1;
- private boolean mCamera = false;
- static String TAG = "myplaterecognizer";
- ImageView show_image;
- Bitmap srcBitmap;
- Bitmap grayBitmap;
- private long mRecognizerPtr = 0;
- private static boolean flag = true;
- TextView Mytv;
- PlateRecognizer myplaterecognizer;
- EditText MyeditText;
- int nRunningOnce = 0;
- String strMyeditText = "1.PNG";
- // String myPath = "/storage/emulated/0/Documents/PlateRcognizer";
- String myPath = "/storage/emulated/0";
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
- Log.d(TAG, "call SetExternalStoragepermission_OK");
- int nRet = 0;//
- SetExternalStoragepermission_OK();
- nRet = CheckExternalStoragepermission_OK();
- if(nRet == 0)
- {
- Log.i("Surface:", "nRet ==0");
- }
- else
- {
- Log.i("Surface:", "nRet !=0");
- Mytv = (TextView) findViewById(R.id.sample_text);
- myplaterecognizer = new PlateRecognizer(this);
- MyeditText = (EditText)findViewById(R.id.editText);
- // str1 = editText1.getText().toString();
- MyeditText.setText(strMyeditText.toCharArray(), 0, strMyeditText.length());
- //initData();
- Mytv.setText(myplaterecognizer.stringFromJNI());
- show_image = (ImageView) findViewById(R.id.imageView);
- } // Example of a call to a native method
- // show_image.setImageResource(R.drawable.heidfk640);
- findViewById(R.id.button_show).setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- //myPath
- String fileName = "";
- strMyeditText = MyeditText.getText().toString();
- fileName = String.format("%s/%s", myPath, strMyeditText);
- String str_test = "";
- str_test = String.format("=1=fileName=%s", fileName);
- Log.d(TAG, str_test);
- Bitmap bm = BitmapFactory.decodeFile(fileName);
- show_image.setImageBitmap(bm);
- }
- });
- findViewById(R.id.button_PlateRecognizer).setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- String fileName = "";
- strMyeditText = MyeditText.getText().toString();
- fileName = String.format("%s/%s", myPath, strMyeditText);
- String str_test = "";
- str_test = String.format("=1=fileName=%s/%s", myPath, strMyeditText);
- Log.d(TAG, str_test);
- // 进行车牌识别 pr_jingph7n90.jpg
- // String plate = myplaterecognizer.recognize("/storage/sdcard/Pictures/PlateRcognizer/heidfk640.jpg");
- String plate = myplaterecognizer.recognize(fileName);
- // String plate = myplaterecognizer.recognize(pictureFile.getAbsolutePath());
- if (null != plate && ! plate.equalsIgnoreCase("0")) {
- Mytv.setText(plate);
- } else {
- Mytv.setText("can not recognize");
- }
- }
- });
- }
- @Override
- protected void onResume() {
- super.onResume();
- if (initDebug()) {
- Log.i(TAG, "OpenCV initialize success");
- } else {
- Log.i(TAG, "OpenCV initialize failed");
- }
- }
- private int CheckExternalStoragepermission_OK()
- {
- String externalStorageState = Environment.getExternalStorageState();
- if (Environment.MEDIA_MOUNTED.equals(externalStorageState)) {
- mExternalStorageAvailable = true;
- mExternalStorageWriteable = true;
- return 2; } else if (Environment.MEDIA_MOUNTED_READ_ONLY.equals(externalStorageState)) {
- mExternalStorageAvailable = true;
- mExternalStorageWriteable = false;
- return 1; } else {
- mExternalStorageAvailable = mExternalStorageWriteable = false;
- return 0;
- }
- // handleExternalStorageState(mExternalStorageAvailable,mExternalStorageWriteable);
- }
- private void SetExternalStoragepermission_OK()
- {
- String externalStorageState = Environment.getExternalStorageState();
- if (Environment.MEDIA_MOUNTED.equals(externalStorageState)) {
- mExternalStorageAvailable = true;
- mExternalStorageWriteable = true;
- } else if (Environment.MEDIA_MOUNTED_READ_ONLY.equals(externalStorageState)) {
- mExternalStorageAvailable = true;
- mExternalStorageWriteable = false;
- } else {
- mExternalStorageAvailable = mExternalStorageWriteable = false;
- }
- handleExternalStorageState(mExternalStorageAvailable,mExternalStorageWriteable);
-
- requestPermission();
-
- }
- private void requestPermission() {
- if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
- != PackageManager.PERMISSION_GRANTED) {
- // 第一次请求权限时,用户如果拒绝,下一次请求shouldShowRequestPermissionRationale()返回true
- // 向用户解释为什么需要这个权限
- if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.CAMERA)) {
- new AlertDialog.Builder(this)
- .setMessage("申请相机权限")
- .setPositiveButton("确定", new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- //申请相机权限
- ActivityCompat.requestPermissions(MainActivity.this,
- new String[]{Manifest.permission.CAMERA}, CAMERA_REQUEST_CODE);
- }
- })
- .show();
- } else {
- //申请相机权限
- ActivityCompat.requestPermissions(this,
- new String[]{Manifest.permission.CAMERA}, CAMERA_REQUEST_CODE);
- }
- } else {
- // Tv.setTextColor(Color.GREEN);
- // Tv.setText("相机权限已申请");
- }
- }
- private void handleCameraState(boolean mCamera ) {
- }
- private void handleExternalStorageState(boolean mExternalStorageAvailable,boolean mExternalStorageWriteable ) {
- if(mExternalStorageAvailable && mExternalStorageWriteable){
- //STORAGE is dangerous so check
- // checkSelfPermission()
- int permissionCheck = ContextCompat.checkSelfPermission(this,
- Manifest.permission.WRITE_EXTERNAL_STORAGE);
- Log.d("permission", "permissionCheck=="+permissionCheck);
- if (permissionCheck != PackageManager.PERMISSION_GRANTED) {
- //has not granted request permission
- // Should we show an explanation?
- if (ActivityCompat.shouldShowRequestPermissionRationale(this,
- Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
- // Show an expanation to the user *asynchronously* -- don't block
- // this thread waiting for the user's response! After the user
- // sees the explanation, try again to request the permission.
- // Toast.makeText(MainActivity.this, "Permission++++", Toast.LENGTH_SHORT).show();
- // ActivityCompat.requestPermissions(this,
- // new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
- // MY_PERMISSIONS_REQUEST_READ_CONTACTS);
- new AlertDialog.Builder(this)
- .setMessage("您拒绝过授予访问外部存储设备的权限,但是只有申请该权限,才能往外部存储设备写入数据,你确定要重新申请获取权限吗?")
- .setPositiveButton("ok", new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- dialog.dismiss();
- //again request permission
- ActivityCompat.requestPermissions(MainActivity.this,
- new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
- MY_PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE);
- }
- })
- .setNegativeButton("no", new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- dialog.dismiss();
- }
- })
- .create()
- .show();
- } else {
- // No explanation needed, we can request the permission.
- ActivityCompat.requestPermissions(this,
- new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
- MY_PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE);
- // MY_PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE is an
- // app-defined int constant. The callback method gets the
- // result of the request.
- }
- }else{
- //had granted
- Toast.makeText(MainActivity.this, "run writeDatasToExternalStorage() method", Toast.LENGTH_SHORT).show();
- // writeDatasToExternalStorage();
- }
- }else{
- Log.d("permission", "ExternalStorage can not be write or unAvailable");
- }
- }
- /** */
- private void writeDatasToExternalStorage(){
- File directory = Environment.getExternalStorageDirectory();// 6.0 /storage/emulated/0 5.0 /storage/sdcard
- /*
- Environment.getDataDirectory();// /data
- Environment.getRootDirectory();// /system
- Environment.getDownloadCacheDirectory();// /cache
- Environment.getExternalStoragePublicDirectory(
- Environment.DIRECTORY_PICTURES);// 6.0 /storage/emulated/0/Pictures 5.0/storage/sdcard/Pictures
- */
- File file = new File(directory, "permisson");
- if(!file.exists()){
- try {
- file.createNewFile();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- }
- /**
- *
- * @param requestCode
- * @param permissions
- * @param grantResults
- */
- @Override
- public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
- Log.d("permission", "onRequestPermissionsResult requestCode" + requestCode);
- if (requestCode == MY_PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE) {
- // If request is cancelled, the result arrays are empty.
- if (grantResults.length > 0
- && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
- // permission was granted, yay! Do the
- // contacts-related task you need to do.
- Toast.makeText(MainActivity.this, "run writeDatasToExternalStorage() method", Toast.LENGTH_SHORT).show();
- // writeDatasToExternalStorage();
- } else {
- // Permission Denied
- Toast.makeText(MainActivity.this, "Permission Denied", Toast.LENGTH_SHORT).show();
- // permission denied, boo! Disable the
- // functionality that depends on this permission.
- }
- return;
- }
- if (requestCode == CAMERA_REQUEST_CODE) {
- if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
- // Tv.setTextColor(Color.GREEN);
- // Tv.setText("相机权限已申请");
- } else {
- //用户勾选了不再询问
- //提示用户手动打开权限
- if (!ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.CAMERA)) {
- Toast.makeText(this, "相机权限已被禁止", Toast.LENGTH_SHORT).show();
- }
- }
- }
- // other 'case' lines to check for other
- // permissions this app might request
- super.onRequestPermissionsResult(requestCode, permissions, grantResults);
- }
- }
复制代码 NDK算法实现; - #include "EasyPR.h"
- #include "easyPR/include/core/plate_locate.h"
- #include "easyPR/include/core/plate_judge.h"
- #include "easyPR/include/core/chars_segment.h"
- #include "easyPR/include/core/chars_identify.h"
- #include "easyPR/include/core/plate_detect.h"
- #include "easyPR/include/core/chars_recognise.h"
- #include "easyPR/include/core/plate_recognize.h"
- using namespace easypr;
- #include <android/log.h>
- #define LOG_TAG "System.out"
- #define LOGI(...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__)
- #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,LOG_TAG,__VA_ARGS__)
- #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)
- extern "C"
- JNIEXPORT jstring JNICALL Java_com_example_gzh_myplaterecognizer_PlateRecognizer_stringFromJNI(
- JNIEnv *env,
- jobject /* this */
- ) {
- return env->NewStringUTF("myplaterecognizer JNI!");
- }
- char* jstring2str(JNIEnv* env, jstring jstr) {
- char* rtn = NULL;
- jclass clsstring = env->FindClass("java/lang/String");
- jstring strencode = env->NewStringUTF("utf-8"); // "GB2312"
- jmethodID mid = env->GetMethodID(clsstring, "getBytes", "(Ljava/lang/String;)[B");
- jbyteArray barr = (jbyteArray) env->CallObjectMethod(jstr, mid, strencode);
- jsize alen = env->GetArrayLength(barr);
- jbyte* ba = env->GetByteArrayElements(barr, JNI_FALSE);
- if (alen > 0) {
- rtn = (char *)malloc(alen + 1);
- memcpy(rtn, ba, alen);
- rtn[alen] = 0;
- }
- env->ReleaseByteArrayElements(barr, ba, 0);
- return rtn;
- }
- extern "C"
- JNIEXPORT jlong JNICALL Java_com_example_gzh_myplaterecognizer_PlateRecognizer_initPR(
- JNIEnv *env,
- jobject /* this */,
- jstring svmpath,
- jstring annpath)
- {
- // const string *svm = (*env)->GetStringUTFChars(env, svmpath, 0);
- // const string *ann = (*env)->GetStringUTFChars(env, annpath, 0);
- char* svm = jstring2str(env, svmpath);
- char* ann = jstring2str(env, annpath);
- LOGE("Java_com_example_gzh_myplaterecognizer_PlateRecognizer_initPR svm=%s, ann=%s", svm, ann);
- CPlateRecognize *pr = new CPlateRecognize();
- pr->setDebug(false);
- pr->setLifemode(true);
- pr->setMaxPlates(4);
- pr->loadSVM(svm);
- pr->loadANN(ann);
- return (jlong)pr;
- }
- extern "C"
- JNIEXPORT jlong JNICALL Java_com_example_gzh_myplaterecognizer_PlateRecognizer_uninitPR(
- JNIEnv *env, jobject, jlong recognizerPtr) {
- CPlateRecognize *pr = (CPlateRecognize *)recognizerPtr;
- LOGE("Java_com_example_gzh_myplaterecognizer_PlateRecognizer_uninitPR");
- delete pr;
- return 0;
- }
- extern "C"
- JNIEXPORT jbyteArray JNICALL Java_com_example_gzh_myplaterecognizer_PlateRecognizer_plateRecognize(
- JNIEnv *env, jobject, jlong recognizerPtr, jstring imgpath) {
- CPlateRecognize *pr = (CPlateRecognize *)recognizerPtr;
- // const string *img = (*env)->GetStringUTFChars(env, imgpath, 0);
- char* img = jstring2str(env, imgpath);
- Mat src = imread(img);
- std::vector<std::string> plateVec;
- int count = pr->plateRecognize(src, plateVec);
- std::string str = "0";
- if(! plateVec.empty() && count == 0) {
- str = plateVec[0];
- }
- char *result = new char[str.length() + 1];
- strcpy(result, str.c_str());
- jbyte *by = (jbyte*) result;
- jbyteArray jarray = env->NewByteArray(strlen(result));
- env->SetByteArrayRegion(jarray, 0, strlen(result), by);
- return jarray;
- }
- extern "C"
- JNIEXPORT jbyteArray JNICALL Java_com_example_gzh_myplaterecognizer_PlateRecognizer_plateRecognize1(
- JNIEnv *env, jobject, jlong recognizerPtr, jlong imgMat) {
- CPlateRecognize *pr = (CPlateRecognize *)recognizerPtr;
- // const string *img = (*env)->GetStringUTFChars(env, imgpath, 0);
- //char* img = jstring2str(env, imgpath);
- Mat& src = *(Mat*)imgMat;// = imread(img);
- std::vector<std::string> plateVec;
- int count = pr->plateRecognize(src, plateVec);
- std::string str = "0";
- if(! plateVec.empty() && count == 0) {
- str = plateVec[0];
- }
- char *result = new char[str.length() + 1];
- strcpy(result, str.c_str());
- jbyte *by = (jbyte*) result;
- jbyteArray jarray = env->NewByteArray(strlen(result));
- env->SetByteArrayRegion(jarray, 0, strlen(result), by);
- return jarray;
- }
复制代码 - #include "../../include/core/plate_recognize.h"
- #include "../../include/config.h"
- namespace easypr {
- CPlateRecognize::CPlateRecognize() { }
- // !车牌识别模块
- int CPlateRecognize::plateRecognize(Mat src, std::vector<std::string> &licenseVec) {
- // 车牌方块集合
- std::vector<CPlate> plateVec;
- // 进行深度定位,使用颜色信息与二次Sobel
- int resultPD = plateDetect(src, plateVec, kDebug, 0);
- if (resultPD == 0) {
- size_t num = plateVec.size();
- int index = 0;
- //依次识别每个车牌内的符号
- for (size_t j = 0; j < num; j++) {
- CPlate item = plateVec[j];
- Mat plate = item.getPlateMat();
- //获取车牌颜色
- std::string plateType = getPlateColor(plate);
- //获取车牌号
- std::string plateIdentify = "";
- int resultCR = charsRecognise(plate, plateIdentify);
- if (resultCR == 0) {
- std::string license = plateType + ":" + plateIdentify;
- licenseVec.push_back(license);
- }
- }
- //完整识别过程到此结束
- //如果是Debug模式,则还需要将定位的图片显示在原图左上角
- if (getPDDebug()) {
- Mat result;
- src.copyTo(result);
- for (size_t j = 0; j < num; j++) {
- CPlate item = plateVec[j];
- Mat plate = item.getPlateMat();
- int height = 36;
- int width = 136;
- if (height * index + height < result.rows) {
- Mat imageRoi = result(Rect(0, 0 + height * index, width, height));
- addWeighted(imageRoi, 0, plate, 1, 0, imageRoi);
- }
- index++;
- RotatedRect minRect = item.getPlatePos();
- Point2f rect_points[4];
- minRect.points(rect_points);
- Scalar lineColor = Scalar(255, 255, 255);
- if (item.getPlateLocateType() == SOBEL) lineColor = Scalar(255, 0, 0);
- if (item.getPlateLocateType() == COLOR) lineColor = Scalar(0, 255, 0);
- for (int j = 0; j < 4; j++)
- line(result, rect_points[j], rect_points[(j + 1) % 4], lineColor, 2,
- 8);
- }
- //显示定位框的图片
- showResult(result);
- }
- }
- return resultPD;
- }
- }
复制代码 四)测试过程和视频 测试过程:1)安装APK;2)准备测试图片 测试视频: http://v.youku.com/v_show/id_XMjYyODcwNzkzNg==.html。 抓取测试图片视频地址: https://pan.baidu.com/s/1pLvxVbd apk共享地址; https://pan.baidu.com/s/1o84Ttvc 五)总结 改进方案: 1)Android界面上应该使用通过按钮选择车牌图片来识别,或者通过Listbox显示多个图片,或者通过Combox选择框来显示多个图片; 2)对plateRecognize类众多函数没有详细解读; 3)固定Camera处理实时图像。 |