From 8bc5187d5e9028e6345fd43cf2199c5e11bf9e88 Mon Sep 17 00:00:00 2001 From: Mathieu Broillet Date: Sun, 4 Aug 2024 18:41:20 +0200 Subject: [PATCH] update readme --- .git-assets/example_grid.webp | Bin 0 -> 15188 bytes .git-assets/example_grid_2.webp | Bin 0 -> 15980 bytes .../preview-webui.webp | Bin README.md | 122 +++++++++++++----- alpr_api.py | 4 +- 5 files changed, 95 insertions(+), 31 deletions(-) create mode 100644 .git-assets/example_grid.webp create mode 100644 .git-assets/example_grid_2.webp rename preview-webui.webp => .git-assets/preview-webui.webp (100%) diff --git a/.git-assets/example_grid.webp b/.git-assets/example_grid.webp new file mode 100644 index 0000000000000000000000000000000000000000..77df269dad2d2fe1df7f48686548fa3c520c7e73 GIT binary patch literal 15188 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 OM}{JgqZxrP0001~LTe=e literal 0 HcmV?d00001 diff --git a/.git-assets/example_grid_2.webp b/.git-assets/example_grid_2.webp new file mode 100644 index 0000000000000000000000000000000000000000..7d1d272df294e5a8ba5e7b0569e924ab242f5eed GIT binary patch literal 15980 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 [!IMPORTANT] -> The ultimateALPR SDK is a lightweight and much faster alternative (on CPU and GPU) to the CodeProject AI software but it has **a few limitations** with it's free version: -> - The last character of the license plate is masked with an asterisk -> - The SDK supposedly has a limit of requests per program execution *(never encountered yet)* **but I have implemented a workaround for this by restarting the SDK after 3000 requests just in case.** +> This project relies on the [ultimateALPR-SDK](https://github.com/DoubangoTelecom/ultimateALPR-SDK), which is a commercial product but has a free version with a few limitations. +> For any commercial use, you will need to take a look at their licensing terms. +> **I am not affiliated with ultimateALPR-SDK in any way, and I am not responsible for any misuse of the software.** +> [!NOTE] +> The [ultimateALPR-SDK](https://github.com/DoubangoTelecom/ultimateALPR-SDK) is a lightweight and much faster alternative (on CPU and GPU) to existing solutions like +> [CodeProject AI](https://www.codeproject.com/AI/docs/index.html) but it has **one important restriction** with it's free version: +> - The last character of the license plate is masked with an asterisk *(e.g. ``ABC1234`` -> ``ABC123*``)* ## Usage -The server listens on port 5000 and has a few endpoints (see below), the most important one being ``/v1/image/alpr``. + +The server listens on port 5000 and has a few endpoints documented below, the most important one being [``/v1/image/alpr``](#v1visionalpr). ### /v1/vision/alpr + > POST: http://localhost:5000/v1/vision/alpr **Description** This endpoint processes an image and returns the license plate information (if any) found in the image. -This endpoint follows the [CodeProject AI ALPR API](https://www.codeproject.com/AI/docs/api/api_reference.html#license-plate-reader) format *(example below)* so it can be used as a **drop-in replacement** for the CodeProject AI software. +This endpoint follows +the [CodeProject AI ALPR API](https://www.codeproject.com/AI/docs/api/api_reference.html#license-plate-reader) format *( +example below)* so it can be used as a **drop-in replacement** for the CodeProject AI software. -**Parameters** -- upload: (File) The image file to process. *(see [Pillow.Image.open()](https://pillow.readthedocs.io/en/stable/reference/Image.html#PIL.Image.open) for supported formats, almost any image format is supported)* -- grid_size: (Integer, optional) Size of grid to divide the image into and retry on each cell when no match have been found on the whole image *(default: 4)* **[(more info)](#more-information-about-the-grid-parameter)** -- wanted_cells: (String, optional) The cells you want to process *(default: all cells)* **[(see here)](#v1visionalpr_grid_debug)** +**Parameters** + +- upload: (File) The image file to process. *( + see [Pillow.Image.open()](https://pillow.readthedocs.io/en/stable/reference/Image.html#PIL.Image.open) for supported + formats, almost any image format is supported)* +- grid_size: (Integer, optional) Size of grid to divide the image into and retry on each cell when no match have been + found on the whole image *(default: 3)* **[(more info)](#more-information-about-the-grid-parameter)** +- wanted_cells: (String, optional) The cells you want to process *(default: all cells)* * + *[(see here)](#v1visionalpr_grid_debug)** - format: ``1,2,3,4,...`` *(comma separated list of integers, max: grid_size^2)* - *Example for a grid_size of 3:* ``` @@ -33,10 +50,10 @@ This endpoint follows the [CodeProject AI ALPR API](https://www.codeproject.com/ 4 | 5 | 6 7 | 8 | 9 ``` - - **Response** -```json +**Response** + +```jsonc { "success": (Boolean) // True if successful. "message": (String) // A summary of the inference operation. @@ -47,41 +64,81 @@ This endpoint follows the [CodeProject AI ALPR API](https://www.codeproject.com/ ``` ### /v1/vision/alpr_grid_debug + > POST: http://localhost:5000/v1/vision/alpr_grid_debug **Description** This endpoint displays the grid and each cell's number on the image. It is intended to be used for debugging purposes to see which cells are being processed. -**Parameters** +**Parameters** *same as [v1/vision/alpr](#v1visionalpr)* - **Response** -```json +**Response** + +```jsonc { "image": (Base64) // The image with the grid and cell numbers drawn on it. } ``` ## More information about the grid parameter -*To write* +When you send an image to the server, sometimes the ALPR software cannot find any plate because the image is too big or +the plate is too small in the image. +To solve this problem, if no plate is found on the whole image, the server will divide the image into a grid of cells +and retry the ALPR software on each cell. +You can specify the size of the grid with the ``grid_size`` parameter in each of your requests. +> [!CAUTION] +> The higher the grid size, the longer the processing time will be. It is recommended to keep the grid size between 3 +> and 4. +> Note: The processing time is in no way multiplied by the grid size (usually takes 2x the time) + +You can speed up the processing time by specifying the ``wanted_cells`` parameter. This parameter allows you to specify +which cells you want to run plate detection on. +This can be useful if you know the plates can only be in certain areas of the image. +> [!TIP] +> You can use the [``/v1/vision/alpr_grid_debug`` endpoint](#v1visionalpr_grid_debug) to see the grid and cell numbers +> overlaid on your image. +> You can then specify the ``wanted_cells`` parameter to only process the cells you want. + +**If you wish not to use the grid, you can set the ``grid_size`` parameter to +0 *(and leave the ``wanted_cells`` parameter empty)*.** + +### Example + +Let's say your driveway camera looks something like this: + +![Driveway camera](.git-assets/example_grid.webp) + +If you set the ``grid_size`` parameter to 2, the image will be divided into a 2x2 grid like this: + +![Driveway camera grid](.git-assets/example_grid_2.webp) + +You can see that cell 1 and 2 are empty and cells 3 and 4 might contain license plates. +You can then set the ``wanted_cells`` parameter to ``3,4`` to only process cells 3 and 4, reducing the processing time +as only half the image will be processed. ## Included models in built executable -When using the built executable, only the **latin** charset models are bundled by default. If you want to use a different -charset, you need to set the charset in the JSON_CONFIG variable and rebuild the executable with the according -models found [here](https://github.com/DoubangoTelecom/ultimateALPR-SDK/tree/master/assets) -To build the executable, you can use the ``build_alpr_api.sh`` script, which will create an executable named ``alpr_api`` in -the ``dist`` folder. + +When using the built executable, only the **latin** charset models are bundled by default. If you want to use a +different charset, you need to set the charset in the JSON_CONFIG variable and rebuild the executable with the +according models found [here](https://github.com/DoubangoTelecom/ultimateALPR-SDK/tree/master/assets) +To build the executable, you can use the ``build_alpr_api.sh`` script, which will create an executable +named ``alpr_api`` in the ``dist`` folder. ## Setup development environment ### Use automatic setup script -You can use the ``build_and_setup_ultimatealvr.sh`` script to automatically install the necessary packages and build the ultimateALPR SDK wheel, copy the assets and the libs. + > [!IMPORTANT] > Make sure to install the package python3-dev (APT) python3-devel (RPM) before running the build and setup script. +> You can use the ``build_and_setup_ultimatealvr.sh`` script to automatically install the necessary packages and build +> the +> ultimateALPR SDK wheel, copy the assets and the libs. The end structure should look like this: + ```bash . ├── alpr_api.py @@ -96,16 +153,23 @@ The end structure should look like this: ``` ### Important notes -When running, building or developing the script, make sure to set the ``LD_LIBRARY_PATH`` environment variable to the libs folder + +When running, building or developing the script, make sure to set the ``LD_LIBRARY_PATH`` environment variable to the +libs folder *(limitation of the ultimateALPR SDK)*. + ```bash export LD_LIBRARY_PATH=libs:$LD_LIBRARY_PATH ``` ### Error handling + #### GLIBC_ABI_DT_RELR not found + If you encounter an error like this: + ```bash /lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_ABI_DT_RELR' not found ``` + Then make sure your GLIBC version is >= 2.36 diff --git a/alpr_api.py b/alpr_api.py index 7eb575f..1cccae4 100644 --- a/alpr_api.py +++ b/alpr_api.py @@ -148,7 +148,7 @@ def create_rest_server_flask(): Parameters: - upload: The image to be processed - - grid_size: The number of cells to split the image into (e.g. 4) + - grid_size: The number of cells to split the image into (e.g. 3) - wanted_cells: The cells to process in the grid separated by commas (e.g. 1,2,3,4) (max: grid_size²) """ interference = time.time() @@ -197,7 +197,7 @@ def create_rest_server_flask(): Parameters: - upload: The image to be processed - - grid_size: The number of cells to split the image into (e.g. 4) + - grid_size: The number of cells to split the image into (e.g. 3) - wanted_cells: The cells to process in the grid separated by commas (e.g. 1,2,3,4) (max: grid_size²) Returns: