This article is the contribution of a friend, he spent a lot of energy to achieve a full function of the didi client, very learning value, recommended to everyone ~
Author: Tan Tuo

This article is long, it is recommended to collect food; Now created an Android development water friends circle, the circle will not regularly update some Android advanced advanced information, welcome everyone with technical problems to discuss, grow together! (Including senior UI engineer, Android underlying development engineer, Android architect, native performance optimization and hybrid optimization, flutter expertise); The hope has the technology big guy to join, the water circle inside solves the problem more obtains the right bigger!

preface

This is an Android travel project imitating Didi Dache made by myself. It mainly adds the following functions to the travel project in view of the “mismatch between people and vehicles” that has been questioned by Didi and other travel platforms, as well as the increasingly popular internationalization and overseas strategy:

  1. RFID identification and verification function: The RFID identification chip is embedded in the driver’s id card or the identification hardware in the car, and the passenger uses the mobile phone to read the chip information, and through the network (OKHTTP3) sent to the travel platform database for verification (I used NDK plus a C language MD5 encryption algorithm to encrypt the identified information). If it is not a compliant “person” or “car”, it cannot complete the order and report its current location to the platform or regulatory authorities. (In order to facilitate readers’ testing, mobile phones can be used to read any encrypted or non-encrypted RFID chip, such as bank card, bus card, etc. I changed the chip information into my own driver information before verification in the code to ensure that readers can receive a reply from the server when testing.
  2. Overseas edition function: click to switch the current language.
  3. Driver id number recognition function: read the id number on the driver id, and can also be used to verify and compare with the information in the travel platform database.
The project source address: https://github.com/18601949127

The project code was typed line by line and tested on multiple mobile phones to ensure the smooth operation of various functions. GitHub source code retains all mobile CPU instruction set architecture, to ensure that all mobile phones can run successfully. If you feel that the package is too big, you can delete the unnecessary.so instruction set by yourself. The main purpose is to identify the opencv4Android. so package is relatively large, followed by baidu Map package.

I used Baidu Map LBS version 5.3 for the map. For overseas users, mapBox is recommended considering the amount of information data, performance, package size, data source and other factors. Interested readers can see Trinea this article: https://blog.csdn.net/weixin_37734988/article/details/92852349

Trinea@Trinea, the author of Didi Internationalization Project Android Terminal Evolution, suggested that I carefully analyze the advantages and disadvantages of several foreign Map services, especially according to the application scenarios of overseas versions. Compare Google Maps, MapBox, Nutiteq, etc. Thank you very much. I’ll write a few separate articles on mapBox usage and share them later

If readers think of practical functions of Didi Chuxing or other platforms, they can leave a message or send it to me on wechat (wechat: 18601949127). I will spare time to add good ideas or functions to the project.

The development environment

1.Android: Android Studio version 3.4, baidu map version 5.3, OpenCV4Android version 3.2

2. Server side: Apache + PHP + MySQL uses Tencent Cloud host which I rent as the server. I will always open the interface of this project and accept and process the test requests sent by readers.

Main interface overview

TitleBar on the top of the interface is the main functional area. In addition to the conspicuous logo in the middle, main functional options are distributed on both sides. SlidingMenu on the left provides the side slide menu, providing access for passenger personal information and software Settings. The English logo button on the right is the international language switch, and the wireless logo on the right is the entrance to the RFID authentication function.

In the middle part of the main interface is the map area, where you can select different vehicles to show the location of passengers, nearby vehicles or POI hot spots, and route planning.

The bottom of the main interface can provide a menu, mainly used for boarding and destination address keyword input, as well as safety information or advertising entrance.

Project file structure

First, introduce the structure of the project file, so that readers can read the code:

Package name: com. Tantuo. Didicar

  • Activities folder: Some activities are independent and do not belong to a function module. You can place them in this folder.
  • Adapter folder: More complex Adapters can be removed from the recycler view class file and saved separately to the Adapter folder, such as the Adapter from the Recycler View drop-down menu on the left. The simpler Adapter will still be stored in the calling class.
  • Bean folder: Store Entity Entity class, such as driver information will be wrapped into a DriverBean, each driver is a class object, use Gson pass is convenient, use get,set can be used.
  • DriverLicenseNFC folder: RFID authentication module used by passengers to verify the driver’s identity or vehicle information.
  • DriverLicenseRecognition folder: function module that identifies driver ID number OCR.
  • Splash folder: App initialization and boot interface.
  • TabFragment: A sliding theme bar at the top of the main screen is used to switch between vehicles or services (Tabs). The codes for different vehicles or services are stored in the TabFragment folder.
  • Utils folder: For example, DriverRouteOverlay is used to render the planned driving route on the map. MD5JniUtils uses NDK to call MD5 encryption algorithm to protect RFID chip information. NfcUtils is used to manage THE NFC function of the mobile phone. POIOverlay is used to render surrounding points of interest (POIS) on the map.
Extracting utility classes from activities or fragments into a unified utils folder will make your code clearer and more readable.

Guide the interface

First look at the effect of the real machine:

The initial logo animation for the boot screen was made by myself using an SVG vector animation. The path plan is described in the drawable splash_logo.xml file:

Didilogoanimator.xml in the animator folder of the resource file is also required to animate the path:

<objectAnimator
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="3000"
    android:propertyName="trimPathEnd"
    android:repeatCount="0"
    android:valueFrom="0"
    android:valueTo="1"
    android:valueType="floatType"/>Copy the code
These few seconds can be used to add initialization code in the locations shown below, such as the network request, getting materials for subsequent activities, geographic location, and so on.

Travel interface

You can switch between different project interfaces by sliding the theme at the top of the map interface. The sliding theme bar is a VIewPager Indicator, and each theme corresponds to one of the following services, placed in its own separate VIewPager. Each service item has its own delimited menu, which can be used as the address key input or relevant information entry of this service.

UI structure of travel interface:

Note: Passenger location information, current latitude and longitude, current street name and building name are all defined as static member variables in the MainAcitivity, because these variables need to be used frequently in other Acitivity or classes. Behind the direct call MainActivity. CurrentLocation is ok, all the current position, are all in the MainActivity MyLocationListener class.

Route planning for boarding addresses and destination addresses

The services for different vehicles (express, taxi, bike, bus, etc.) are embedded in the VIewpager below the TItleBar. Each service corresponds to a separate Fragmen file, which is swiped by the VIewpagerIndicator at the top. The main code for the service project is in the TabFragment folder under the com.tantuo. Didicar package.

Bottom slide menu

Buttonsheet is android in the layout file support. The v4. Widget. NestedScrollView bottom_sheet_behavior class

app:layout_behavior="@string/bottom_sheet_behavior"Copy the code

Left side slide menu

The left side menu can be used as an entry point for personal information, safety tips and Settings

Driver id number OCR identification function

The main code of the license number identification function is in the DriverLicenseRecognition module of com.tantuo. Didicar package.

Or first look at the real machine effect:

After entering the identification function of driver’s id number, you can choose to take photos of the id. To facilitate demonstration, here you can select the photos just taken from the mobile phone album. For the convenience of the reader to test the function at the same time, I keep the photos in the asset development kit folder inside, so readers download me keep on making version of https://github.com/18601949127, Click select driver certificate is invoked after I save the driver in the assets folder id photo, which is in the picture below getDriverLicenseFromMySample () method, which can be tested immediately. Readers who want to continue reading from the phone album can run the LicenseMainActivity method under LicenseMainActivity.

Here are my credentials from my previous study abroad as an example:

First: look for the driver’s id area in the photo, which is the red area around the edge of the id

/** * Get the image gradient of driver's id in RGB color space, and then do binarization on this image. Thus, the certificate area was obtained by contour discovery and area size filtering * author:Tantuo 86-1860194917 * @param fileUri * @return*/ public Mat findLicenseContour(Uri fileUri) {// Use openCV Imgcodecs to get the id image from the camera Imgcodecs.imread(fileUri.getPath());if (src.empty()) {
            returnnull; } // get the x gradient and y gradient of the id photo Mat grad_x = new Mat(); Mat grad_y = new Mat(); Mat abs_grad_x = new Mat(); Mat abs_grad_y = new Mat(); // The gradient operation is the derivative operation of pixels in the image. By :Tantuo imgproc. Scharr(SRC, grad_x, cvtype.cv_32f, 1, 0); Imgproc.Scharr(src, grad_y, CvType.CV_32F, 0, 1); // CvType in openCV with 32-bit floating point numbers is used to hold pixel data values that may be negative core.convertScaleabs (grad_x, abs_grad_x); Core.convertScaleAbs(grad_y, abs_grad_y); OpenCV uses release() to release the Mat image and recycle() to release the BitMap image grad_x.release(); grad_y.release(); Mat grad = new Mat(); Mat grad = new Mat(); Mat grad = new Mat(); AddWeighted (abs_grad_x, 0.5, abs_grad_y, 0.5, 0, grad); abs_grad_x.release(); abs_grad_y.release(); Imgproc.cvtcolor binary = new Mat(); // imgproc.cvtcolor binary = new Mat(); Imgproc.cvtColor(grad, binary, Imgproc.COLOR_BGR2GRAY); Imgproc.threshold(binary, binary, 40, 255, imgproc. THRESH_BINARY); grad.release(); // Run the following morphology excution to remove the noise of binary image. First to get the Size of 3 * 3 pixel structure elements Mat kernel = Imgproc. GetStructuringElement (Imgproc MORPH_RECT, new Size (3, 3)); // Then perform the Morph_open operation on the structure elements. Imgproc.morphologyEx(binary, binary, imgproc. MORPH_OPEN, kernel); // Next use openCV's imgproc.findContours () method to find the contour of the driver's id in the image; Mat hierarchy = new Mat(); Imgproc.findContours(binary, contours, hierarchy, Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_SIMPLE, new Point(0, 0)); int hw = binary.cols() / 2; int hh = binary.rows() / 2; Rect roi = new Rect();for(int i = 0; i < contours.size(); i++) { Rect rect = Imgproc.boundingRect(contours.get(i)); // If the contour width of a certain ROI interest area is found to be more than half of the picture, it can be considered as the contour of driver's certificateif (rect.width > hw) {
                roi.x = rect.x;
                roi.y = rect.y;
                roi.width = rect.width;
                roi.height = rect.height;
                break; }} // Return if not foundif (roi.width == 0 || roi.height == 0) {
            returnnull; } Mat card = new Mat(); src.submat(roi).copyTo(card); // Remember to release the resource 0 src.release(); binary.release();return card;
    }Copy the code
The first step is to call imgproc.scharr () method to perform Scharr gradient operation on the original photo of driver’s ID card. The so-called gradient operation is to perform derivative operation on pixels in the image, so as to obtain the difference value of two adjacent pixels. The big difference of pixels is contour in the image. The second step is to make Binarization on the image, call imgproc.morphologyex () method, discover and filter the id area by contour.

Imgproc.cvtcolor () is called after the edge is discovered to get the following id area:

2. After identifying the certificate area, we noticed that there was an eye-catching rectangle in the upper left corner of the certificate, which was used as reference to identify the number area containing numbers at the bottom of the photo. In the program this procedure calls the findCardNumBlock(Mat Card) method below.

Public Mat findCardNumBlock(Mat card) {// Initialize HSV color space (H: Hue,S:saturation,V:value brightness, brightness); Mat binary = new Mat(); Imgproc.COLOR_BGR2HSV imgproc. cvtColor(card, HSV, imgproc. COLOR_BGR2HSV); //inThe Range function will filter the root threshold of the HSV color image, which will be used to filter out colors that are not helpful in identifying the marked area in the upper left corner // and save the filtered image in binary // Scalar () is a structure with three parameters, which represent the hue, saturation of HSV, Core. InRange (HSV, New Scalar(30, 40, 45), New Scalar(180, 255, 255), binary); // The above will get a binary image of the driver's id, However, there is a lot of noise. // A morphology excution is carried out on the binary image to remove the small 5*5 structural elements (noise) Mat kernel = Imgproc.getStructuringElement(Imgproc.MORPH_RECT, new Size(5, 5)); Imgproc.morphologyEx(binary, binary, Imgproc.MORPH_OPEN, kernel); List<MatOfPoint> contours = new ArrayList<>(); Mat hierarchy = new Mat(); Imgproc.findContours(binary, contours, hierarchy, Imgproc.RETR_EXTERNAL, Imgproc.CHAIN_APPROX_SIMPLE, new Point(0, 0)); int offsetx = binary.cols() / 3; int offsety = binary.rows() / 3; Rect numberROI = new Rect(); // Find the enclosing rectangle contourArea for each ROIfor(int i = 0; i < contours.size(); i++) { Rect roi = Imgproc.boundingRect(contours.get(i)); // If the identified rectangular area is too small (less than 200 pixels), ignore itif (Imgproc.contourArea(contours.get(i)) < 200) {
                continue; } // Once you have found the mark area, base on the mark area, the id number should be located at the mark x *2 and the width should be about bine.cols () -roi.x -100 pixels // The id number should be about 0.7 times the height of the mark (base) *0.7; // If the outline of the marker in the upper left corner is less than one third of the length and width of the document, the marker will be used as the standard for the numbered areaif(roi.x < offsetx && roi.y < offsety) { numberROI.x = 3* roi.x + 120; numberROI.y = roi.y + 4 * roi.height - 65; numberROI.width = binary.cols() - roi.x - 390; Height = (int) (ROI. Height * 0.9);break; }} // Return null if not foundif (numberROI.width == 0 || numberROI.height == 0) {
            returnnull; } textimage = new Mat(); card.submat(numberROI).copyTo(textImage); Release mat resource card.release(); hsv.release(); binary.release();return textImage;
    }Copy the code
Imgproc class generates a imgProc.color_bgr2HSV object by calling OpenCV Imgproc class.

Imgproc.cvtColor(card, hsv, Imgproc.COLOR_BGR2HSV);Copy the code
Then call the core.inrange () method below to get a range of HSV colors within the New Scalar(30, 40, 45), which is the Reference rectangle in the upper left corner.

Core.inRange(hsv, new Scalar(30, 40, 45), new Scalar(180, 255, 255), binary);Copy the code
The next step is to remove noise by morphological operation. Noise refers to the small black spots identified in the binarization image. Morphology excution will cover these small black spots in the image with the color of a large area next to them, in order to make the processed image easier to be recognized by the machine. For example, the following code calls OpenCV’s imgProc.morphologyex () method to compensate for structural elements of size 5 by 5 (noise) with surrounding pixels

// The following morphology excution is carried out on the binarized image to remove the small 5*5 structural elements (noise) Mat kernel = Imgproc.getStructuringElement(Imgproc.MORPH_RECT, new Size(5, 5)); Imgproc.morphologyEx(binary, binary, Imgproc.MORPH_OPEN, kernel); After noise processing, start to find the number area in the ID card area to find the Contour operation: // Find the outer rectangle contourArea for each ROIfor(int i = 0; i < contours.size(); i++) { Rect roi = Imgproc.boundingRect(contours.get(i)); // If the identified rectangular area is too small (less than 200 pixels), ignore itif (Imgproc.contourArea(contours.get(i)) < 200) {
                continue; } // Once you have found the mark area, base on the mark area, the id number should be located at the mark x *2 and the width should be about bine.cols () -roi.x -100 pixels // The id number should be about 0.7 times the height of the mark (base) *0.7; // If the outline of the marker in the upper left corner is less than one third of the length and width of the document, the marker will be used as the standard for the numbered areaif(roi.x < offsetx && roi.y < offsety) { numberROI.x = 3* roi.x + 120; numberROI.y = roi.y + 4 * roi.height - 65; numberROI.width = binary.cols() - roi.x - 390; Height = (int) (ROI. Height * 0.9);break; }}Copy the code
The area with the id number can then be intercepted and saved to textimage

Mat textImage = new Mat(); card.submat(numberROI).copyTo(textImage); Release mat resource card.release(); hsv.release(); binary.release();return textImage;Copy the code
After completing the above work, you can recognize the rectangular outline of the id number area:

The DigitImageProcessor class is called to identify these numbers after identifying the number area in the certificate. This process needs to be described separately in another article. The functions of several important methods are described below:

  • The splitNumberBlock(Mat textImage) method uses binarization to identify character Outlines in numeric regions.
  • The getSplitLinePos(Mat Mtexts) method is used to separate two numbers bonded together in the image.
  • The function of extractFeatureData(Mat txtImage) method is to extract the feature of card number recognition, and obtain the black pixel feature of each number of card number, which is an important basis for the feature and recognition of each number.
  • The dumpFeature(float[] fv, int textNum) method saves the generated feature text file on the phone.
  • ReadFeatureVector (File f) is used to read saved feature vectors.
  • RecognitionChar (Mat charImage) is used to identify the id number according to the feature vector.
RFID identification and verification function

The main code for RIFD authentication is in the DriverLIcenseNFC module of com.tantuo. Didicar:

Or first look at the real machine effect:

After clicking on the RFID verification entry in the upper right corner, passengers will be prompted to use the back of the phone as if swiping a bus card to sense THE RFID hardware, such as the driver’s identification card embedded with a chip, fixed in the car identifier, etc.

Note: In order for an Activity to receive RFID chip numbers at the top of the current stack, it must set the intent filter in the manifest. XML file to block TAGDISCOVERED actions so that the Activity can capture RFID tag information. And set LunchMode to SingleTop to ensure that RFID tag information is captured again by the Activity at the top of the stack, instead of pushing it onto the stack and fetching a new DriverRFIDMainActivity to the top. For those wondering, check out the Activity launch mode and stack management article.

Links: Thoroughly understand the application scenarios of Activity launch modes -SingleTop, SingleTask, and SingleInstance https://blog.csdn.net/weixin_37734988/article/details/93508139

Considering that the user’s mobile phone may have multiple apps or activities that can intercept RFID or NFC chip information, it is necessary to set enableForegroundDispatch to DriverRFIDMainActivity at the top of the stack. This ensures that when an RFID tag is detected, this Activity has the highest capture priority, rather than having the Android Activity scheduling mechanism call up a new Activity with blocking permission.

@Override
    protected void onResume() { super.onResume(); // Foreground distribution system, used to ensure the highest capture priority when detecting RFID tags. NfcUtils.mNfcAdapter.enableForegroundDispatch(this, NfcUtils.mPendingIntent, NfcUtils.mIntentFilter, NfcUtils.mTechList);
    }
    @Override
    protected void onPause() { super.onPause(); // Shut down the foreground scheduling system NfcUtils.mNfcAdapter.disableForegroundDispatch(this);
    }Copy the code
The code for reading chip ID for mobile phone is put separately in NfcUtils utility class under utils folder.

Mobile phones to read to the chip information, will be called the NDK compiled into the C language of MD5 encryption algorithm so file (finally speak), together with the longitude and latitude location at that time to send to the platform server (I use OkHttp3), and compare the database of registered in the driver’s information, and the verification results and driver information sent to the passengers:

I use my own Tencent cloud host + Apache + PHP+ MySQL on the server side. I will always open the network interface of this project and maintain it continuously, so as to facilitate readers to test this function. As long as readers read any RFID embedded encryption chip such as student ID card, bank card or bus card with their mobile phones during the verification process, the program will change the ID information read into the author’s own before sending the data request (the highlighted part in the second line of the code below), and then send it to the platform server for verification. So the reader reading any RFID information on the phone will receive the driver’s information sent back from the server. Comment out this line in the actual project.

After receiving the verification request sent by the passenger, the server will verify it against the platform driver database, and send the verification result and the corresponding driver and vehicle information back to the passenger. The following is the registration information database of registered drivers on the platform server. I made some screenshots with Navicat. The red part in the first line is the result of platform verification, which is the author’s own information:

The server will also sort out and analyze the data sent by passengers, and can also send the data and location information of “person-vehicle discrepancy” to the compliance department.

The following figure is the regional thermal map of “vehicle-person mismatch” :

In addition, the heat map of car demand in peak hours can be screened out according to the time of passengers’ booking, providing data support to the driver scheduling department.

In Python, Pandas, and MatplotLib, you can easily process the data on the server side and create a visual diagram of the data on the server side.

The NDK is used to invoke the MD5 encryption algorithm

As mentioned above, ID numbers will be encrypted with MD5 algorithm of C language in the project. The key codes are shown in the CPP folder below. The NDK Components in the figure provide a complete set of tools to compile the C language dynamic library (so) and package it into APK. The following md5. h and md5. CPP files are algorithm header files and source files written in C language respectively. The header file is used to declare variables, types, and macro definitions to be used in the source file. The source file is used to describe methods and implementations, including a #include”MD5.h” to import the header file. The relationship between the two is a bit like that between the contents of a book and the contents of a book.

There is also a native-lib. CPP file below the MD5 C file, which the NDK helped us generate in Android Studio. It can be thought of as a bridge between Java methods and C methods. The following figure shows how native lib helps MD5JniUtils class getMd5 () call C encryption methods. The JNIEXPORT and JNICALL macros are used to identify functions that call the. So library. Just like C++ can call a.dll dynamic link library, the function name is followed by the naming rules are important: Java_ + package name + Java tool class name + Java call method, the following variable parameter is Java String corresponding to JNI jString, below in the method body, can use the C language to encrypt the String before the encryption operation. And returns the encrypted jString result to the Java layer.

I need more video and document information of the private message, more advanced and interview information please enter the circle to receive, welcome to rational discussion, have questions welcome to leave a message to discuss.