├── Android └── placeholder.txt ├── LICENSE ├── README.md ├── TFLite_detection_image.py ├── TFLite_detection_stream.py ├── TFLite_detection_video.py ├── TFLite_detection_webcam.py ├── Train_TFLite1_Object_Detection_Model.ipynb ├── Train_TFLite2_Object_Detction_Model.ipynb ├── deploy_guides ├── MacOS_TFLite_Guide.md ├── Raspberry_Pi_Guide.md └── Windows_TFLite_Guide.md ├── doc ├── BSR_demo.gif ├── BSR_directory1.png ├── Coral_and_EdgeTPU2.png ├── MSYS_window.png ├── TFL_download_links.png ├── TFLite-vs-EdgeTPU.gif ├── YouTube_video1.JPG ├── YouTube_video2.png ├── calculate-mAP-demo1.gif ├── camera_enabled.png ├── colab_upload_button.png ├── labeled_image_example2.png ├── labeled_image_examples.png ├── labelmap_example.png ├── local_training_guide.md ├── object_detection_folder.png ├── squirrels!!.png ├── tflite1_folder.png └── training_in_progress.png ├── examples ├── ChangeCounter.py └── README.md ├── get_pi_requirements.sh ├── test.mp4 ├── test1.jpg └── util_scripts ├── README.md ├── calculate_map_cartucho.py ├── create_csv.py ├── create_tfrecord.py ├── train_val_test_split.py └── train_val_test_split_yolo.py /Android/placeholder.txt: -------------------------------------------------------------------------------- 1 | This is a placeholder... the Android content will come eventually! 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TensorFlow Lite Object Detection on Android and Raspberry Pi 2 | Train your own TensorFlow Lite object detection models and run them on the Raspberry Pi, Android phones, and other edge devices! 3 | 4 |

5 | 6 |

7 | 8 | Get started with training on Google Colab by clicking the icon below, or [click here to go straight to the YouTube video that provides step-by-step instructions](https://youtu.be/XZ7FYAMCc4M). 9 | 10 | Open In Colab 11 | 12 | ## Introduction 13 | TensorFlow Lite is an optimized framework for deploying lightweight deep learning models on resource-constrained edge devices. TensorFlow Lite models have faster inference time and require less processing power than regular TensorFlow models, so they can be used to obtain faster performance in realtime applications. 14 | 15 | This guide provides step-by-step instructions for how train a custom TensorFlow Object Detection model, convert it into an optimized format that can be used by TensorFlow Lite, and run it on edge devices like the Raspberry Pi. It also provides Python code for running TensorFlow Lite models to perform detection on images, videos, web streams, or webcam feeds. 16 | 17 | ## Step 1. Train TensorFlow Lite Models 18 | ### Using Google Colab (recommended) 19 | 20 | The easiest way to train, convert, and export a TensorFlow Lite model is using Google Colab. Colab provides you with a free GPU-enabled virtual machine on Google's servers that comes pre-installed with the libraries and packages needed for training. 21 | 22 | I wrote a [Google Colab notebook](./Train_TFLite2_Object_Detction_Model.ipynb) that can be used to train custom TensorFlow Lite models. It goes through the process of preparing data, configuring a model for training, training the model, running it on test images, and exporting it to a downloadable TFLite format so you can deploy it to your own device. It makes training a custom TFLite model as easy as uploading an image dataset and clicking Play on a few blocks of code! 23 | 24 | Open In Colab 25 | 26 | Open the Colab notebook in your browser by clicking the icon above. Work through the instructions in the notebook to start training your own model. Once it's trained and exported, visit the [Setup TFLite Runtime Environment](#step-2-setup-tflite-runtime-environment-on-your-device) section to learn how to deploy it on your PC, Raspberry Pi, Android phone, or other edge devices. 27 | 28 | ### Using a Local PC 29 | The old version of this guide shows how to set up a TensorFlow training environment locally on your PC. Be warned: it's a lot of work, and the guide is outdated. [Here's a link to the local training guide.](doc/local_training_guide.md) 30 | 31 | ## Step 2. Setup TFLite Runtime Environment on Your Device 32 | Once you have a trained `.tflite` model, the next step is to deploy it on a device like a computer, Raspberry Pi, or Android phone. To run the model, you'll need to install the TensorFlow or the TensorFlow Lite Runtime on your device and set up the Python environment and directory structure to run your application in. The [deploy_guides](deploy_guides) folder in this repository has step-by-step guides showing how to set up a TensorFlow environment on several different devices. Links to the guides are given below. 33 | 34 | ### Raspberry Pi 35 | Follow the [Raspberry Pi setup guide](deploy_guides/Raspberry_Pi_Guide.md) to install TFLite Runtime on a Raspberry Pi 3 or 4 and run a TensorFlow Lite model. This guide also shows how to use the Google Coral USB Accelerator to greatly increase the speed of quantized models on the Raspberry Pi. 36 | 37 | ### Windows 38 | Follow the instructions in the [Windows TFLite guide](deploy_guides/Windows_TFLite_Guide.md) to set up TFLite Runtime on your Windows PC using Anaconda! 39 | 40 | ### macOS 41 | Still to come! 42 | 43 | ### Linux 44 | Still to come! 45 | 46 | ### Android 47 | Still to come! 48 | 49 | ### Embedded Devices 50 | Still to come! 51 | 52 | ## Step 3. Run TensorFlow Lite Models! 53 | There are four Python scripts to run the TensorFlow Lite object detection model on an image, video, web stream, or webcam feed. The scripts are based off the label_image.py example given in the [TensorFlow Lite examples GitHub repository](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/lite/examples/python/label_image.py). 54 | 55 | * [TFLite_detection_image.py](TFLite_detection_image.py) 56 | * [TFLite_detection_video.py](TFLite_detection_video.py) 57 | * [TFLite_detection_stream.py](TFLite_detection_stream.py) 58 | * [TFLite_detection_webcam.py](TFLite_detection_webcam.py) 59 | 60 | The following instructions show how to run the scripts. These instructions assume your .tflite model file and labelmap.txt file are in the `TFLite_model` folder in your `tflite1` directory as per the instructions given in the [Setup TFLite Runtime Environment](#step-2-setup-tflite-runtime-environment-on-your-device) guide. 61 | 62 |

63 | 64 |

65 | 66 | If you’d like try using the sample TFLite object detection model provided by Google, simply download it [here](https://storage.googleapis.com/download.tensorflow.org/models/tflite/coco_ssd_mobilenet_v1_1.0_quant_2018_06_29.zip), unzip it to the `tflite1` folder, and rename it to `TFLite_model`. Then, use `--modeldir=coco_ssd_mobilenet_v1_1.0_quant_2018_06_29` rather than `--modeldir=TFLite_model` when running the script. 67 | 68 |
69 | Webcam 70 | Make sure you have a USB webcam plugged into your computer. If you’re on a laptop with a built-in camera, you don’t need to plug in a USB webcam. 71 | 72 | From the `tflite1` directory, issue: 73 | 74 | ``` 75 | python TFLite_detection_webcam.py --modeldir=TFLite_model 76 | ``` 77 | 78 | After a few moments of initializing, a window will appear showing the webcam feed. Detected objects will have bounding boxes and labels displayed on them in real time. 79 |
80 | 81 |
82 | Video 83 | To run the video detection script, issue: 84 | 85 | ``` 86 | python TFLite_detection_video.py --modeldir=TFLite_model 87 | ``` 88 | 89 | A window will appear showing consecutive frames from the video, with each object in the frame labeled. Press 'q' to close the window and end the script. By default, the video detection script will open a video named 'test.mp4'. To open a specific video file, use the `--video` option: 90 | 91 | ``` 92 | python TFLite_detection_video.py --modeldir=TFLite_model --video='birdy.mp4' 93 | ``` 94 | 95 | Note: Video detection will run at a slower FPS than realtime webcam detection. This is mainly because loading a frame from a video file requires more processor I/O than receiving a frame from a webcam. 96 |
97 | 98 |
99 | Web stream 100 | To run the script to detect images in a video stream (e.g. a remote security camera), issue: 101 | 102 | ``` 103 | python TFLite_detection_stream.py --modeldir=TFLite_model --streamurl="http://ipaddress:port/stream/video.mjpeg" 104 | ``` 105 | 106 | After a few moments of initializing, a window will appear showing the video stream. Detected objects will have bounding boxes and labels displayed on them in real time. 107 | 108 | Make sure to update the URL parameter to the one that is being used by your security camera. It has to include authentication information in case the stream is secured. 109 | 110 | If the bounding boxes are not matching the detected objects, probably the stream resolution wasn't detected. In this case you can set it explicitly by using the `--resolution` parameter: 111 | 112 | ``` 113 | python TFLite_detection_stream.py --modeldir=TFLite_model --streamurl="http://ipaddress:port/stream/video.mjpeg" --resolution=1920x1080 114 | ``` 115 |
116 | 117 |
118 | Image 119 | To run the image detection script, issue: 120 | 121 | ``` 122 | python TFLite_detection_image.py --modeldir=TFLite_model 123 | ``` 124 | 125 | The image will appear with all objects labeled. Press 'q' to close the image and end the script. By default, the image detection script will open an image named 'test1.jpg'. To open a specific image file, use the `--image` option: 126 | 127 | ``` 128 | python TFLite_detection_image.py --modeldir=TFLite_model --image=squirrel.jpg 129 | ``` 130 | 131 | It can also open an entire folder full of images and perform detection on each image. There can only be images files in the folder, or errors will occur. To specify which folder has images to perform detection on, use the `--imagedir` option: 132 | 133 | ``` 134 | python TFLite_detection_image.py --modeldir=TFLite_model --imagedir=squirrels 135 | ``` 136 | 137 | Press any key (other than 'q') to advance to the next image. Do not use both the --image option and the --imagedir option when running the script, or it will throw an error. 138 | 139 | To save labeled images and a text file with detection results for each image, use the `--save_results` option. The results will be saved to a folder named `_results`. This works well if you want to check your model's performance on a folder of images and use the results to calculate mAP with the [calculate_map_catchuro.py](./util_scripts) script. For example: 140 | 141 | ``` 142 | python TFLite_detection_image.py --modeldir=TFLite_model --imagedir=squirrels --save_results 143 | ``` 144 | 145 | The `--noshow_results` option will stop the program from displaying images. 146 |
147 | 148 | **See all command options** 149 | 150 | For more information on options that can be used while running the scripts, use the `-h` option when calling them. For example: 151 | 152 | ``` 153 | python TFLite_detection_image.py -h 154 | ``` 155 | 156 | If you encounter errors, please check the [FAQ section](https://github.com/EdjeElectronics/TensorFlow-Lite-Object-Detection-on-Android-and-Raspberry-Pi#FAQs) of this guide. It has a list of common errors and their solutions. If you can successfully run the script, but your object isn’t detected, it is most likely because your model isn’t accurate enough. The FAQ has further discussion on how to resolve this. 157 | 158 | ## Examples 159 | (Still to come!) Please see the [examples](examples) folder for examples of how to use your TFLite model in basic vision applications. 160 | 161 | ## FAQs 162 |
163 | What's the difference between the TensorFlow Object Detection API and TFLite Model Maker? 164 |
165 | Google provides a set of Colab notebooks for training TFLite models called [TFLite Model Maker](https://www.tensorflow.org/lite/models/modify/model_maker). While their object detection notebook is straightfoward and easy to follow, using the [TensorFlow Object Detection API](https://github.com/tensorflow/models/tree/master/research/object_detection) for creating models provides several benefits: 166 | 167 | * TFLite Model Maker only supports EfficientDet models, which aren't as fast as SSD-MobileNet models. 168 | * Training models with the Object Detection API generally results in better model accuracy. 169 | * The Object Detection API provides significantly more flexibility in model and training configuration (training steps, learning rate, model depth and resolution, etc). 170 | * Google still [recommends using the Object Detection API](https://www.tensorflow.org/lite/examples/object_detection/overview#fine-tuning_models_on_custom_data) as the formal method for training models with large datasets. 171 |
172 | 173 |
174 | What's the difference between training, transfer learning, and fine-tuning? 175 |
176 | Using correct terminology is important in a complicated field like machine learning. In this notebook, I use the word "training" to describe the process of teaching a model to recognize custom objects, but what we're actually doing is "fine-tuning". The Keras documentation gives a [good example notebook](https://keras.io/guides/transfer_learning/) explaining the difference between each term. 177 | 178 | Here's my attempt at defining the terms: 179 | 180 | * **Training**: The process of taking a full neural network with randomly initialized weights, passing in image data, calculating the resulting loss from its predictions on those images, and using backpropagation to adjust the weights in every node of the network and reduce its loss. In this process, the network learns how to extract features of interest from images and correlate those features to classes. Training a model from scratch typically takes millions of training steps and a large dataset of 100,000+ images (such as ImageNet or COCO). Let's leave actual training to companies like Google and Microsoft! 181 | * **Transfer learning**: Taking a model that has already been trained, unfreezing the last layer of the model (i.e. making it so only the last layer's weights can be modified), and retraining the last layer with a new dataset so it can learn to identify new classes. Transfer learning takes advantage of the feature extraction capabilities that have already been learned in the deep layers of the trained model. It takes the extracted features and recategorizes them to predict new classes. 182 | * **Fine-tuning**: Fine-tuning is similar to transfer learning, except more layers are unfrozen and retrained. Instead of just unfreezing the last layer, a significant amount of layers (such as the last 20% to 50% of layers) are unfrozen. This allows the model to modify some of its feature extraction layers so it can extract features that are more relevant to the classes its trying to identify. This notebook (and the TensorFlow Object Detection API) uses fine-tuning. 183 | 184 | In general, I like to use the word "training" instead of "fine-tuning", because it's more intuitive and understandable to new users. 185 |
186 | 187 |
188 | Should I get a Google Colab Pro subscription? 189 |
190 | If you plan to use Colab frequently for training models, I recommend getting a Colab Pro subscription. It provides several benefits: 191 | 192 | * Idle Colab sessions remain connected for longer before timing out and disconnecting 193 | * Allows for running multiple Colab sessions at once 194 | * Priority access to TPU and GPU-enabled virtual machines 195 | * Virtual machines have more RAM 196 | 197 | Colab keeps track of how much GPU time you use, and cuts you off from using GPU-enabled instances once you reach a certain use time. If you get the message telling you you're cut off from GPU instances, then that's a good indicator that you use Colab enough to justify paying for a Pro subscription. 198 |
199 | -------------------------------------------------------------------------------- /TFLite_detection_image.py: -------------------------------------------------------------------------------- 1 | ######## Webcam Object Detection Using Tensorflow-trained Classifier ######### 2 | # 3 | # Author: Evan Juras 4 | # Date: 11/11/22 5 | # Description: 6 | # This program uses a TensorFlow Lite object detection model to perform object 7 | # detection on an image or a folder full of images. It draws boxes and scores 8 | # around the objects of interest in each image. 9 | # 10 | # This code is based off the TensorFlow Lite image classification example at: 11 | # https://github.com/tensorflow/tensorflow/blob/master/tensorflow/lite/examples/python/label_image.py 12 | # 13 | # I added my own method of drawing boxes and labels using OpenCV. 14 | 15 | # Import packages 16 | import os 17 | import argparse 18 | import cv2 19 | import numpy as np 20 | import sys 21 | import glob 22 | import importlib.util 23 | 24 | 25 | # Define and parse input arguments 26 | parser = argparse.ArgumentParser() 27 | parser.add_argument('--modeldir', help='Folder the .tflite file is located in', 28 | required=True) 29 | parser.add_argument('--graph', help='Name of the .tflite file, if different than detect.tflite', 30 | default='detect.tflite') 31 | parser.add_argument('--labels', help='Name of the labelmap file, if different than labelmap.txt', 32 | default='labelmap.txt') 33 | parser.add_argument('--threshold', help='Minimum confidence threshold for displaying detected objects', 34 | default=0.5) 35 | parser.add_argument('--image', help='Name of the single image to perform detection on. To run detection on multiple images, use --imagedir', 36 | default=None) 37 | parser.add_argument('--imagedir', help='Name of the folder containing images to perform detection on. Folder must contain only images.', 38 | default=None) 39 | parser.add_argument('--save_results', help='Save labeled images and annotation data to a results folder', 40 | action='store_true') 41 | parser.add_argument('--noshow_results', help='Don\'t show result images (only use this if --save_results is enabled)', 42 | action='store_false') 43 | parser.add_argument('--edgetpu', help='Use Coral Edge TPU Accelerator to speed up detection', 44 | action='store_true') 45 | 46 | args = parser.parse_args() 47 | 48 | 49 | # Parse user inputs 50 | MODEL_NAME = args.modeldir 51 | GRAPH_NAME = args.graph 52 | LABELMAP_NAME = args.labels 53 | 54 | min_conf_threshold = float(args.threshold) 55 | use_TPU = args.edgetpu 56 | 57 | save_results = args.save_results # Defaults to False 58 | show_results = args.noshow_results # Defaults to True 59 | 60 | IM_NAME = args.image 61 | IM_DIR = args.imagedir 62 | 63 | # If both an image AND a folder are specified, throw an error 64 | if (IM_NAME and IM_DIR): 65 | print('Error! Please only use the --image argument or the --imagedir argument, not both. Issue "python TFLite_detection_image.py -h" for help.') 66 | sys.exit() 67 | 68 | # If neither an image or a folder are specified, default to using 'test1.jpg' for image name 69 | if (not IM_NAME and not IM_DIR): 70 | IM_NAME = 'test1.jpg' 71 | 72 | # Import TensorFlow libraries 73 | # If tflite_runtime is installed, import interpreter from tflite_runtime, else import from regular tensorflow 74 | # If using Coral Edge TPU, import the load_delegate library 75 | pkg = importlib.util.find_spec('tflite_runtime') 76 | if pkg: 77 | from tflite_runtime.interpreter import Interpreter 78 | if use_TPU: 79 | from tflite_runtime.interpreter import load_delegate 80 | else: 81 | from tensorflow.lite.python.interpreter import Interpreter 82 | if use_TPU: 83 | from tensorflow.lite.python.interpreter import load_delegate 84 | 85 | # If using Edge TPU, assign filename for Edge TPU model 86 | if use_TPU: 87 | # If user has specified the name of the .tflite file, use that name, otherwise use default 'edgetpu.tflite' 88 | if (GRAPH_NAME == 'detect.tflite'): 89 | GRAPH_NAME = 'edgetpu.tflite' 90 | 91 | 92 | # Get path to current working directory 93 | CWD_PATH = os.getcwd() 94 | 95 | # Define path to images and grab all image filenames 96 | if IM_DIR: 97 | PATH_TO_IMAGES = os.path.join(CWD_PATH,IM_DIR) 98 | images = glob.glob(PATH_TO_IMAGES + '/*.jpg') + glob.glob(PATH_TO_IMAGES + '/*.png') + glob.glob(PATH_TO_IMAGES + '/*.bmp') 99 | if save_results: 100 | RESULTS_DIR = IM_DIR + '_results' 101 | 102 | elif IM_NAME: 103 | PATH_TO_IMAGES = os.path.join(CWD_PATH,IM_NAME) 104 | images = glob.glob(PATH_TO_IMAGES) 105 | if save_results: 106 | RESULTS_DIR = 'results' 107 | 108 | # Create results directory if user wants to save results 109 | if save_results: 110 | RESULTS_PATH = os.path.join(CWD_PATH,RESULTS_DIR) 111 | if not os.path.exists(RESULTS_PATH): 112 | os.makedirs(RESULTS_PATH) 113 | 114 | # Path to .tflite file, which contains the model that is used for object detection 115 | PATH_TO_CKPT = os.path.join(CWD_PATH,MODEL_NAME,GRAPH_NAME) 116 | 117 | # Path to label map file 118 | PATH_TO_LABELS = os.path.join(CWD_PATH,MODEL_NAME,LABELMAP_NAME) 119 | 120 | # Load the label map 121 | with open(PATH_TO_LABELS, 'r') as f: 122 | labels = [line.strip() for line in f.readlines()] 123 | 124 | # Have to do a weird fix for label map if using the COCO "starter model" from 125 | # https://www.tensorflow.org/lite/models/object_detection/overview 126 | # First label is '???', which has to be removed. 127 | if labels[0] == '???': 128 | del(labels[0]) 129 | 130 | # Load the Tensorflow Lite model. 131 | # If using Edge TPU, use special load_delegate argument 132 | if use_TPU: 133 | interpreter = Interpreter(model_path=PATH_TO_CKPT, 134 | experimental_delegates=[load_delegate('libedgetpu.so.1.0')]) 135 | print(PATH_TO_CKPT) 136 | else: 137 | interpreter = Interpreter(model_path=PATH_TO_CKPT) 138 | 139 | interpreter.allocate_tensors() 140 | 141 | # Get model details 142 | input_details = interpreter.get_input_details() 143 | output_details = interpreter.get_output_details() 144 | height = input_details[0]['shape'][1] 145 | width = input_details[0]['shape'][2] 146 | 147 | floating_model = (input_details[0]['dtype'] == np.float32) 148 | 149 | input_mean = 127.5 150 | input_std = 127.5 151 | 152 | # Check output layer name to determine if this model was created with TF2 or TF1, 153 | # because outputs are ordered differently for TF2 and TF1 models 154 | outname = output_details[0]['name'] 155 | 156 | if ('StatefulPartitionedCall' in outname): # This is a TF2 model 157 | boxes_idx, classes_idx, scores_idx = 1, 3, 0 158 | else: # This is a TF1 model 159 | boxes_idx, classes_idx, scores_idx = 0, 1, 2 160 | 161 | # Loop over every image and perform detection 162 | for image_path in images: 163 | 164 | # Load image and resize to expected shape [1xHxWx3] 165 | image = cv2.imread(image_path) 166 | image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) 167 | imH, imW, _ = image.shape 168 | image_resized = cv2.resize(image_rgb, (width, height)) 169 | input_data = np.expand_dims(image_resized, axis=0) 170 | 171 | # Normalize pixel values if using a floating model (i.e. if model is non-quantized) 172 | if floating_model: 173 | input_data = (np.float32(input_data) - input_mean) / input_std 174 | 175 | # Perform the actual detection by running the model with the image as input 176 | interpreter.set_tensor(input_details[0]['index'],input_data) 177 | interpreter.invoke() 178 | 179 | # Retrieve detection results 180 | boxes = interpreter.get_tensor(output_details[boxes_idx]['index'])[0] # Bounding box coordinates of detected objects 181 | classes = interpreter.get_tensor(output_details[classes_idx]['index'])[0] # Class index of detected objects 182 | scores = interpreter.get_tensor(output_details[scores_idx]['index'])[0] # Confidence of detected objects 183 | 184 | detections = [] 185 | 186 | # Loop over all detections and draw detection box if confidence is above minimum threshold 187 | for i in range(len(scores)): 188 | if ((scores[i] > min_conf_threshold) and (scores[i] <= 1.0)): 189 | 190 | # Get bounding box coordinates and draw box 191 | # Interpreter can return coordinates that are outside of image dimensions, need to force them to be within image using max() and min() 192 | ymin = int(max(1,(boxes[i][0] * imH))) 193 | xmin = int(max(1,(boxes[i][1] * imW))) 194 | ymax = int(min(imH,(boxes[i][2] * imH))) 195 | xmax = int(min(imW,(boxes[i][3] * imW))) 196 | 197 | cv2.rectangle(image, (xmin,ymin), (xmax,ymax), (10, 255, 0), 2) 198 | 199 | # Draw label 200 | object_name = labels[int(classes[i])] # Look up object name from "labels" array using class index 201 | label = '%s: %d%%' % (object_name, int(scores[i]*100)) # Example: 'person: 72%' 202 | labelSize, baseLine = cv2.getTextSize(label, cv2.FONT_HERSHEY_SIMPLEX, 0.7, 2) # Get font size 203 | label_ymin = max(ymin, labelSize[1] + 10) # Make sure not to draw label too close to top of window 204 | cv2.rectangle(image, (xmin, label_ymin-labelSize[1]-10), (xmin+labelSize[0], label_ymin+baseLine-10), (255, 255, 255), cv2.FILLED) # Draw white box to put label text in 205 | cv2.putText(image, label, (xmin, label_ymin-7), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 0), 2) # Draw label text 206 | 207 | detections.append([object_name, scores[i], xmin, ymin, xmax, ymax]) 208 | 209 | # All the results have been drawn on the image, now display the image 210 | if show_results: 211 | cv2.imshow('Object detector', image) 212 | 213 | # Press any key to continue to next image, or press 'q' to quit 214 | if cv2.waitKey(0) == ord('q'): 215 | break 216 | 217 | # Save the labeled image to results folder if desired 218 | if save_results: 219 | 220 | # Get filenames and paths 221 | image_fn = os.path.basename(image_path) 222 | image_savepath = os.path.join(CWD_PATH,RESULTS_DIR,image_fn) 223 | 224 | base_fn, ext = os.path.splitext(image_fn) 225 | txt_result_fn = base_fn +'.txt' 226 | txt_savepath = os.path.join(CWD_PATH,RESULTS_DIR,txt_result_fn) 227 | 228 | # Save image 229 | cv2.imwrite(image_savepath, image) 230 | 231 | # Write results to text file 232 | # (Using format defined by https://github.com/Cartucho/mAP, which will make it easy to calculate mAP) 233 | with open(txt_savepath,'w') as f: 234 | for detection in detections: 235 | f.write('%s %.4f %d %d %d %d\n' % (detection[0], detection[1], detection[2], detection[3], detection[4], detection[5])) 236 | 237 | # Clean up 238 | cv2.destroyAllWindows() 239 | -------------------------------------------------------------------------------- /TFLite_detection_stream.py: -------------------------------------------------------------------------------- 1 | ######## Video Stream Object Detection Using Tensorflow-trained Classifier ######### 2 | # 3 | # Author: Evan Juras (update by JanT) 4 | # Date: 10/27/19 (updated 12/4/2019) 5 | # Description: 6 | # This program uses a TensorFlow Lite model to perform object detection on a live video stream. 7 | # It draws boxes and scores around the objects of interest in each frame from the 8 | # stream. To improve FPS, the webcam object runs in a separate thread from the main program. 9 | # This script will work with codecs supported by CV2 (e.g. MJPEG, RTSP, ...). 10 | # 11 | # This code is based off the TensorFlow Lite image classification example at: 12 | # https://github.com/tensorflow/tensorflow/blob/master/tensorflow/lite/examples/python/label_image.py 13 | # 14 | # I added my own method of drawing boxes and labels using OpenCV. 15 | 16 | # Import packages 17 | import os 18 | import argparse 19 | import cv2 20 | import numpy as np 21 | import sys 22 | import time 23 | from threading import Thread 24 | import importlib.util 25 | 26 | # Define VideoStream class to handle streaming of video from webcam in separate processing thread 27 | # Source - Adrian Rosebrock, PyImageSearch: https://www.pyimagesearch.com/2015/12/28/increasing-raspberry-pi-fps-with-python-and-opencv/ 28 | class VideoStream: 29 | """Camera object that controls video streaming""" 30 | def __init__(self,resolution=(640,480),framerate=30): 31 | # Initialize the PiCamera and the camera image stream 32 | self.stream = cv2.VideoCapture(STREAM_URL) 33 | ret = self.stream.set(cv2.CAP_PROP_FOURCC, cv2.VideoWriter_fourcc(*'MJPG')) 34 | ret = self.stream.set(3,resolution[0]) 35 | ret = self.stream.set(4,resolution[1]) 36 | 37 | # Read first frame from the stream 38 | (self.grabbed, self.frame) = self.stream.read() 39 | 40 | # Variable to control when the camera is stopped 41 | self.stopped = False 42 | 43 | def start(self): 44 | # Start the thread that reads frames from the video stream 45 | Thread(target=self.update,args=()).start() 46 | return self 47 | 48 | def update(self): 49 | # Keep looping indefinitely until the thread is stopped 50 | while True: 51 | # If the camera is stopped, stop the thread 52 | if self.stopped: 53 | # Close camera resources 54 | self.stream.release() 55 | return 56 | 57 | # Otherwise, grab the next frame from the stream 58 | (self.grabbed, self.frame) = self.stream.read() 59 | 60 | def read(self): 61 | # Return the most recent frame 62 | return self.frame 63 | 64 | def stop(self): 65 | # Indicate that the camera and thread should be stopped 66 | self.stopped = True 67 | 68 | # Define and parse input arguments 69 | parser = argparse.ArgumentParser() 70 | parser.add_argument('--modeldir', help='Folder the .tflite file is located in', 71 | required=True) 72 | parser.add_argument('--streamurl', help='The full URL of the video stream e.g. http://ipaddress:port/stream/video.mjpeg', 73 | required=True) 74 | parser.add_argument('--graph', help='Name of the .tflite file, if different than detect.tflite', 75 | default='detect.tflite') 76 | parser.add_argument('--labels', help='Name of the labelmap file, if different than labelmap.txt', 77 | default='labelmap.txt') 78 | parser.add_argument('--threshold', help='Minimum confidence threshold for displaying detected objects', 79 | default=0.5) 80 | parser.add_argument('--resolution', help='Desired webcam resolution in WxH. If the webcam does not support the resolution entered, errors may occur.', 81 | default='1280x720') 82 | parser.add_argument('--edgetpu', help='Use Coral Edge TPU Accelerator to speed up detection', 83 | action='store_true') 84 | 85 | args = parser.parse_args() 86 | 87 | MODEL_NAME = args.modeldir 88 | STREAM_URL = args.streamurl 89 | GRAPH_NAME = args.graph 90 | LABELMAP_NAME = args.labels 91 | min_conf_threshold = float(args.threshold) 92 | resW, resH = args.resolution.split('x') 93 | imW, imH = int(resW), int(resH) 94 | use_TPU = args.edgetpu 95 | 96 | # Import TensorFlow libraries 97 | # If tflite_runtime is installed, import interpreter from tflite_runtime, else import from regular tensorflow 98 | # If using Coral Edge TPU, import the load_delegate library 99 | pkg = importlib.util.find_spec('tflite_runtime') 100 | if pkg: 101 | from tflite_runtime.interpreter import Interpreter 102 | if use_TPU: 103 | from tflite_runtime.interpreter import load_delegate 104 | else: 105 | from tensorflow.lite.python.interpreter import Interpreter 106 | if use_TPU: 107 | from tensorflow.lite.python.interpreter import load_delegate 108 | 109 | # If using Edge TPU, assign filename for Edge TPU model 110 | if use_TPU: 111 | # If user has specified the name of the .tflite file, use that name, otherwise use default 'edgetpu.tflite' 112 | if (GRAPH_NAME == 'detect.tflite'): 113 | GRAPH_NAME = 'edgetpu.tflite' 114 | 115 | # Get path to current working directory 116 | CWD_PATH = os.getcwd() 117 | 118 | # Path to .tflite file, which contains the model that is used for object detection 119 | PATH_TO_CKPT = os.path.join(CWD_PATH,MODEL_NAME,GRAPH_NAME) 120 | 121 | # Path to label map file 122 | PATH_TO_LABELS = os.path.join(CWD_PATH,MODEL_NAME,LABELMAP_NAME) 123 | 124 | # Load the label map 125 | with open(PATH_TO_LABELS, 'r') as f: 126 | labels = [line.strip() for line in f.readlines()] 127 | 128 | # Have to do a weird fix for label map if using the COCO "starter model" from 129 | # https://www.tensorflow.org/lite/models/object_detection/overview 130 | # First label is '???', which has to be removed. 131 | if labels[0] == '???': 132 | del(labels[0]) 133 | 134 | # Load the Tensorflow Lite model. 135 | # If using Edge TPU, use special load_delegate argument 136 | if use_TPU: 137 | interpreter = Interpreter(model_path=PATH_TO_CKPT, 138 | experimental_delegates=[load_delegate('libedgetpu.so.1.0')]) 139 | print(PATH_TO_CKPT) 140 | else: 141 | interpreter = Interpreter(model_path=PATH_TO_CKPT) 142 | 143 | interpreter.allocate_tensors() 144 | 145 | # Get model details 146 | input_details = interpreter.get_input_details() 147 | output_details = interpreter.get_output_details() 148 | height = input_details[0]['shape'][1] 149 | width = input_details[0]['shape'][2] 150 | 151 | floating_model = (input_details[0]['dtype'] == np.float32) 152 | 153 | input_mean = 127.5 154 | input_std = 127.5 155 | 156 | # Check output layer name to determine if this model was created with TF2 or TF1, 157 | # because outputs are ordered differently for TF2 and TF1 models 158 | outname = output_details[0]['name'] 159 | 160 | if ('StatefulPartitionedCall' in outname): # This is a TF2 model 161 | boxes_idx, classes_idx, scores_idx = 1, 3, 0 162 | else: # This is a TF1 model 163 | boxes_idx, classes_idx, scores_idx = 0, 1, 2 164 | 165 | # Initialize frame rate calculation 166 | frame_rate_calc = 1 167 | freq = cv2.getTickFrequency() 168 | 169 | # Initialize video stream 170 | videostream = VideoStream(resolution=(imW,imH),framerate=30).start() 171 | time.sleep(1) 172 | 173 | #for frame1 in camera.capture_continuous(rawCapture, format="bgr",use_video_port=True): 174 | while True: 175 | 176 | # Start timer (for calculating frame rate) 177 | t1 = cv2.getTickCount() 178 | 179 | # Grab frame from video stream 180 | frame1 = videostream.read() 181 | 182 | # Acquire frame and resize to expected shape [1xHxWx3] 183 | frame = frame1.copy() 184 | frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) 185 | frame_resized = cv2.resize(frame_rgb, (width, height)) 186 | input_data = np.expand_dims(frame_resized, axis=0) 187 | 188 | # Normalize pixel values if using a floating model (i.e. if model is non-quantized) 189 | if floating_model: 190 | input_data = (np.float32(input_data) - input_mean) / input_std 191 | 192 | # Perform the actual detection by running the model with the image as input 193 | interpreter.set_tensor(input_details[0]['index'],input_data) 194 | interpreter.invoke() 195 | 196 | # Retrieve detection results 197 | boxes = interpreter.get_tensor(output_details[boxes_idx]['index'])[0] # Bounding box coordinates of detected objects 198 | classes = interpreter.get_tensor(output_details[classes_idx]['index'])[0] # Class index of detected objects 199 | scores = interpreter.get_tensor(output_details[scores_idx]['index'])[0] # Confidence of detected objects 200 | 201 | # Loop over all detections and draw detection box if confidence is above minimum threshold 202 | for i in range(len(scores)): 203 | if ((scores[i] > min_conf_threshold) and (scores[i] <= 1.0)): 204 | 205 | # Get bounding box coordinates and draw box 206 | # Interpreter can return coordinates that are outside of image dimensions, need to force them to be within image using max() and min() 207 | ymin = int(max(1,(boxes[i][0] * imH))) 208 | xmin = int(max(1,(boxes[i][1] * imW))) 209 | ymax = int(min(imH,(boxes[i][2] * imH))) 210 | xmax = int(min(imW,(boxes[i][3] * imW))) 211 | 212 | cv2.rectangle(frame, (xmin,ymin), (xmax,ymax), (10, 255, 0), 2) 213 | 214 | # Draw label 215 | object_name = labels[int(classes[i])] # Look up object name from "labels" array using class index 216 | label = '%s: %d%%' % (object_name, int(scores[i]*100)) # Example: 'person: 72%' 217 | labelSize, baseLine = cv2.getTextSize(label, cv2.FONT_HERSHEY_SIMPLEX, 0.7, 2) # Get font size 218 | label_ymin = max(ymin, labelSize[1] + 10) # Make sure not to draw label too close to top of window 219 | cv2.rectangle(frame, (xmin, label_ymin-labelSize[1]-10), (xmin+labelSize[0], label_ymin+baseLine-10), (255, 255, 255), cv2.FILLED) # Draw white box to put label text in 220 | cv2.putText(frame, label, (xmin, label_ymin-7), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 0), 2) # Draw label text 221 | 222 | # Draw framerate in corner of frame 223 | cv2.putText(frame,'FPS: {0:.2f}'.format(frame_rate_calc),(30,50),cv2.FONT_HERSHEY_SIMPLEX,1,(255,255,0),2,cv2.LINE_AA) 224 | 225 | # All the results have been drawn on the frame, so it's time to display it. 226 | cv2.imshow('Object detector', frame) 227 | 228 | # Calculate framerate 229 | t2 = cv2.getTickCount() 230 | time1 = (t2-t1)/freq 231 | frame_rate_calc= 1/time1 232 | 233 | # Press 'q' to quit 234 | if cv2.waitKey(1) == ord('q'): 235 | break 236 | 237 | # Clean up 238 | cv2.destroyAllWindows() 239 | videostream.stop() 240 | -------------------------------------------------------------------------------- /TFLite_detection_video.py: -------------------------------------------------------------------------------- 1 | ######## Webcam Object Detection Using Tensorflow-trained Classifier ######### 2 | # 3 | # Author: Evan Juras 4 | # Date: 10/2/19 5 | # Description: 6 | # This program uses a TensorFlow Lite model to perform object detection on a 7 | # video. It draws boxes and scores around the objects of interest in each frame 8 | # from the video. 9 | # 10 | # This code is based off the TensorFlow Lite image classification example at: 11 | # https://github.com/tensorflow/tensorflow/blob/master/tensorflow/lite/examples/python/label_image.py 12 | # 13 | # I added my own method of drawing boxes and labels using OpenCV. 14 | 15 | # Import packages 16 | import os 17 | import argparse 18 | import cv2 19 | import numpy as np 20 | import sys 21 | import importlib.util 22 | 23 | 24 | 25 | # Define and parse input arguments 26 | parser = argparse.ArgumentParser() 27 | parser.add_argument('--modeldir', help='Folder the .tflite file is located in', 28 | required=True) 29 | parser.add_argument('--graph', help='Name of the .tflite file, if different than detect.tflite', 30 | default='detect.tflite') 31 | parser.add_argument('--labels', help='Name of the labelmap file, if different than labelmap.txt', 32 | default='labelmap.txt') 33 | parser.add_argument('--threshold', help='Minimum confidence threshold for displaying detected objects', 34 | default=0.5) 35 | parser.add_argument('--video', help='Name of the video file', 36 | default='test.mp4') 37 | parser.add_argument('--edgetpu', help='Use Coral Edge TPU Accelerator to speed up detection', 38 | action='store_true') 39 | 40 | args = parser.parse_args() 41 | 42 | MODEL_NAME = args.modeldir 43 | GRAPH_NAME = args.graph 44 | LABELMAP_NAME = args.labels 45 | VIDEO_NAME = args.video 46 | min_conf_threshold = float(args.threshold) 47 | use_TPU = args.edgetpu 48 | 49 | # Import TensorFlow libraries 50 | # If tflite_runtime is installed, import interpreter from tflite_runtime, else import from regular tensorflow 51 | # If using Coral Edge TPU, import the load_delegate library 52 | pkg = importlib.util.find_spec('tflite_runtime') 53 | if pkg: 54 | from tflite_runtime.interpreter import Interpreter 55 | if use_TPU: 56 | from tflite_runtime.interpreter import load_delegate 57 | else: 58 | from tensorflow.lite.python.interpreter import Interpreter 59 | if use_TPU: 60 | from tensorflow.lite.python.interpreter import load_delegate 61 | 62 | # If using Edge TPU, assign filename for Edge TPU model 63 | if use_TPU: 64 | # If user has specified the name of the .tflite file, use that name, otherwise use default 'edgetpu.tflite' 65 | if (GRAPH_NAME == 'detect.tflite'): 66 | GRAPH_NAME = 'edgetpu.tflite' 67 | 68 | # Get path to current working directory 69 | CWD_PATH = os.getcwd() 70 | 71 | # Path to video file 72 | VIDEO_PATH = os.path.join(CWD_PATH,VIDEO_NAME) 73 | 74 | # Path to .tflite file, which contains the model that is used for object detection 75 | PATH_TO_CKPT = os.path.join(CWD_PATH,MODEL_NAME,GRAPH_NAME) 76 | 77 | # Path to label map file 78 | PATH_TO_LABELS = os.path.join(CWD_PATH,MODEL_NAME,LABELMAP_NAME) 79 | 80 | # Load the label map 81 | with open(PATH_TO_LABELS, 'r') as f: 82 | labels = [line.strip() for line in f.readlines()] 83 | 84 | # Have to do a weird fix for label map if using the COCO "starter model" from 85 | # https://www.tensorflow.org/lite/models/object_detection/overview 86 | # First label is '???', which has to be removed. 87 | if labels[0] == '???': 88 | del(labels[0]) 89 | 90 | # Load the Tensorflow Lite model. 91 | # If using Edge TPU, use special load_delegate argument 92 | if use_TPU: 93 | interpreter = Interpreter(model_path=PATH_TO_CKPT, 94 | experimental_delegates=[load_delegate('libedgetpu.so.1.0')]) 95 | print(PATH_TO_CKPT) 96 | else: 97 | interpreter = Interpreter(model_path=PATH_TO_CKPT) 98 | 99 | interpreter.allocate_tensors() 100 | 101 | # Get model details 102 | input_details = interpreter.get_input_details() 103 | output_details = interpreter.get_output_details() 104 | height = input_details[0]['shape'][1] 105 | width = input_details[0]['shape'][2] 106 | 107 | floating_model = (input_details[0]['dtype'] == np.float32) 108 | 109 | input_mean = 127.5 110 | input_std = 127.5 111 | 112 | # Check output layer name to determine if this model was created with TF2 or TF1, 113 | # because outputs are ordered differently for TF2 and TF1 models 114 | outname = output_details[0]['name'] 115 | 116 | if ('StatefulPartitionedCall' in outname): # This is a TF2 model 117 | boxes_idx, classes_idx, scores_idx = 1, 3, 0 118 | else: # This is a TF1 model 119 | boxes_idx, classes_idx, scores_idx = 0, 1, 2 120 | 121 | # Open video file 122 | video = cv2.VideoCapture(VIDEO_PATH) 123 | imW = video.get(cv2.CAP_PROP_FRAME_WIDTH) 124 | imH = video.get(cv2.CAP_PROP_FRAME_HEIGHT) 125 | 126 | while(video.isOpened()): 127 | 128 | # Acquire frame and resize to expected shape [1xHxWx3] 129 | ret, frame = video.read() 130 | if not ret: 131 | print('Reached the end of the video!') 132 | break 133 | frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) 134 | frame_resized = cv2.resize(frame_rgb, (width, height)) 135 | input_data = np.expand_dims(frame_resized, axis=0) 136 | 137 | # Normalize pixel values if using a floating model (i.e. if model is non-quantized) 138 | if floating_model: 139 | input_data = (np.float32(input_data) - input_mean) / input_std 140 | 141 | # Perform the actual detection by running the model with the image as input 142 | interpreter.set_tensor(input_details[0]['index'],input_data) 143 | interpreter.invoke() 144 | 145 | # Retrieve detection results 146 | boxes = interpreter.get_tensor(output_details[boxes_idx]['index'])[0] # Bounding box coordinates of detected objects 147 | classes = interpreter.get_tensor(output_details[classes_idx]['index'])[0] # Class index of detected objects 148 | scores = interpreter.get_tensor(output_details[scores_idx]['index'])[0] # Confidence of detected objects 149 | 150 | # Loop over all detections and draw detection box if confidence is above minimum threshold 151 | for i in range(len(scores)): 152 | if ((scores[i] > min_conf_threshold) and (scores[i] <= 1.0)): 153 | 154 | # Get bounding box coordinates and draw box 155 | # Interpreter can return coordinates that are outside of image dimensions, need to force them to be within image using max() and min() 156 | ymin = int(max(1,(boxes[i][0] * imH))) 157 | xmin = int(max(1,(boxes[i][1] * imW))) 158 | ymax = int(min(imH,(boxes[i][2] * imH))) 159 | xmax = int(min(imW,(boxes[i][3] * imW))) 160 | 161 | cv2.rectangle(frame, (xmin,ymin), (xmax,ymax), (10, 255, 0), 4) 162 | 163 | # Draw label 164 | object_name = labels[int(classes[i])] # Look up object name from "labels" array using class index 165 | label = '%s: %d%%' % (object_name, int(scores[i]*100)) # Example: 'person: 72%' 166 | labelSize, baseLine = cv2.getTextSize(label, cv2.FONT_HERSHEY_SIMPLEX, 0.7, 2) # Get font size 167 | label_ymin = max(ymin, labelSize[1] + 10) # Make sure not to draw label too close to top of window 168 | cv2.rectangle(frame, (xmin, label_ymin-labelSize[1]-10), (xmin+labelSize[0], label_ymin+baseLine-10), (255, 255, 255), cv2.FILLED) # Draw white box to put label text in 169 | cv2.putText(frame, label, (xmin, label_ymin-7), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 0), 2) # Draw label text 170 | 171 | # All the results have been drawn on the frame, so it's time to display it. 172 | cv2.imshow('Object detector', frame) 173 | 174 | # Press 'q' to quit 175 | if cv2.waitKey(1) == ord('q'): 176 | break 177 | 178 | # Clean up 179 | video.release() 180 | cv2.destroyAllWindows() 181 | -------------------------------------------------------------------------------- /TFLite_detection_webcam.py: -------------------------------------------------------------------------------- 1 | ######## Webcam Object Detection Using Tensorflow-trained Classifier ######### 2 | # 3 | # Author: Evan Juras 4 | # Date: 10/27/19 5 | # Description: 6 | # This program uses a TensorFlow Lite model to perform object detection on a live webcam 7 | # feed. It draws boxes and scores around the objects of interest in each frame from the 8 | # webcam. To improve FPS, the webcam object runs in a separate thread from the main program. 9 | # This script will work with either a Picamera or regular USB webcam. 10 | # 11 | # This code is based off the TensorFlow Lite image classification example at: 12 | # https://github.com/tensorflow/tensorflow/blob/master/tensorflow/lite/examples/python/label_image.py 13 | # 14 | # I added my own method of drawing boxes and labels using OpenCV. 15 | 16 | # Import packages 17 | import os 18 | import argparse 19 | import cv2 20 | import numpy as np 21 | import sys 22 | import time 23 | from threading import Thread 24 | import importlib.util 25 | 26 | # Define VideoStream class to handle streaming of video from webcam in separate processing thread 27 | # Source - Adrian Rosebrock, PyImageSearch: https://www.pyimagesearch.com/2015/12/28/increasing-raspberry-pi-fps-with-python-and-opencv/ 28 | class VideoStream: 29 | """Camera object that controls video streaming from the Picamera""" 30 | def __init__(self,resolution=(640,480),framerate=30): 31 | # Initialize the PiCamera and the camera image stream 32 | self.stream = cv2.VideoCapture(0) 33 | ret = self.stream.set(cv2.CAP_PROP_FOURCC, cv2.VideoWriter_fourcc(*'MJPG')) 34 | ret = self.stream.set(3,resolution[0]) 35 | ret = self.stream.set(4,resolution[1]) 36 | 37 | # Read first frame from the stream 38 | (self.grabbed, self.frame) = self.stream.read() 39 | 40 | # Variable to control when the camera is stopped 41 | self.stopped = False 42 | 43 | def start(self): 44 | # Start the thread that reads frames from the video stream 45 | Thread(target=self.update,args=()).start() 46 | return self 47 | 48 | def update(self): 49 | # Keep looping indefinitely until the thread is stopped 50 | while True: 51 | # If the camera is stopped, stop the thread 52 | if self.stopped: 53 | # Close camera resources 54 | self.stream.release() 55 | return 56 | 57 | # Otherwise, grab the next frame from the stream 58 | (self.grabbed, self.frame) = self.stream.read() 59 | 60 | def read(self): 61 | # Return the most recent frame 62 | return self.frame 63 | 64 | def stop(self): 65 | # Indicate that the camera and thread should be stopped 66 | self.stopped = True 67 | 68 | # Define and parse input arguments 69 | parser = argparse.ArgumentParser() 70 | parser.add_argument('--modeldir', help='Folder the .tflite file is located in', 71 | required=True) 72 | parser.add_argument('--graph', help='Name of the .tflite file, if different than detect.tflite', 73 | default='detect.tflite') 74 | parser.add_argument('--labels', help='Name of the labelmap file, if different than labelmap.txt', 75 | default='labelmap.txt') 76 | parser.add_argument('--threshold', help='Minimum confidence threshold for displaying detected objects', 77 | default=0.5) 78 | parser.add_argument('--resolution', help='Desired webcam resolution in WxH. If the webcam does not support the resolution entered, errors may occur.', 79 | default='1280x720') 80 | parser.add_argument('--edgetpu', help='Use Coral Edge TPU Accelerator to speed up detection', 81 | action='store_true') 82 | 83 | args = parser.parse_args() 84 | 85 | MODEL_NAME = args.modeldir 86 | GRAPH_NAME = args.graph 87 | LABELMAP_NAME = args.labels 88 | min_conf_threshold = float(args.threshold) 89 | resW, resH = args.resolution.split('x') 90 | imW, imH = int(resW), int(resH) 91 | use_TPU = args.edgetpu 92 | 93 | # Import TensorFlow libraries 94 | # If tflite_runtime is installed, import interpreter from tflite_runtime, else import from regular tensorflow 95 | # If using Coral Edge TPU, import the load_delegate library 96 | pkg = importlib.util.find_spec('tflite_runtime') 97 | if pkg: 98 | from tflite_runtime.interpreter import Interpreter 99 | if use_TPU: 100 | from tflite_runtime.interpreter import load_delegate 101 | else: 102 | from tensorflow.lite.python.interpreter import Interpreter 103 | if use_TPU: 104 | from tensorflow.lite.python.interpreter import load_delegate 105 | 106 | # If using Edge TPU, assign filename for Edge TPU model 107 | if use_TPU: 108 | # If user has specified the name of the .tflite file, use that name, otherwise use default 'edgetpu.tflite' 109 | if (GRAPH_NAME == 'detect.tflite'): 110 | GRAPH_NAME = 'edgetpu.tflite' 111 | 112 | # Get path to current working directory 113 | CWD_PATH = os.getcwd() 114 | 115 | # Path to .tflite file, which contains the model that is used for object detection 116 | PATH_TO_CKPT = os.path.join(CWD_PATH,MODEL_NAME,GRAPH_NAME) 117 | 118 | # Path to label map file 119 | PATH_TO_LABELS = os.path.join(CWD_PATH,MODEL_NAME,LABELMAP_NAME) 120 | 121 | # Load the label map 122 | with open(PATH_TO_LABELS, 'r') as f: 123 | labels = [line.strip() for line in f.readlines()] 124 | 125 | # Have to do a weird fix for label map if using the COCO "starter model" from 126 | # https://www.tensorflow.org/lite/models/object_detection/overview 127 | # First label is '???', which has to be removed. 128 | if labels[0] == '???': 129 | del(labels[0]) 130 | 131 | # Load the Tensorflow Lite model. 132 | # If using Edge TPU, use special load_delegate argument 133 | if use_TPU: 134 | interpreter = Interpreter(model_path=PATH_TO_CKPT, 135 | experimental_delegates=[load_delegate('libedgetpu.so.1.0')]) 136 | print(PATH_TO_CKPT) 137 | else: 138 | interpreter = Interpreter(model_path=PATH_TO_CKPT) 139 | 140 | interpreter.allocate_tensors() 141 | 142 | # Get model details 143 | input_details = interpreter.get_input_details() 144 | output_details = interpreter.get_output_details() 145 | height = input_details[0]['shape'][1] 146 | width = input_details[0]['shape'][2] 147 | 148 | floating_model = (input_details[0]['dtype'] == np.float32) 149 | 150 | input_mean = 127.5 151 | input_std = 127.5 152 | 153 | # Check output layer name to determine if this model was created with TF2 or TF1, 154 | # because outputs are ordered differently for TF2 and TF1 models 155 | outname = output_details[0]['name'] 156 | 157 | if ('StatefulPartitionedCall' in outname): # This is a TF2 model 158 | boxes_idx, classes_idx, scores_idx = 1, 3, 0 159 | else: # This is a TF1 model 160 | boxes_idx, classes_idx, scores_idx = 0, 1, 2 161 | 162 | # Initialize frame rate calculation 163 | frame_rate_calc = 1 164 | freq = cv2.getTickFrequency() 165 | 166 | # Initialize video stream 167 | videostream = VideoStream(resolution=(imW,imH),framerate=30).start() 168 | time.sleep(1) 169 | 170 | #for frame1 in camera.capture_continuous(rawCapture, format="bgr",use_video_port=True): 171 | while True: 172 | 173 | # Start timer (for calculating frame rate) 174 | t1 = cv2.getTickCount() 175 | 176 | # Grab frame from video stream 177 | frame1 = videostream.read() 178 | 179 | # Acquire frame and resize to expected shape [1xHxWx3] 180 | frame = frame1.copy() 181 | frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) 182 | frame_resized = cv2.resize(frame_rgb, (width, height)) 183 | input_data = np.expand_dims(frame_resized, axis=0) 184 | 185 | # Normalize pixel values if using a floating model (i.e. if model is non-quantized) 186 | if floating_model: 187 | input_data = (np.float32(input_data) - input_mean) / input_std 188 | 189 | # Perform the actual detection by running the model with the image as input 190 | interpreter.set_tensor(input_details[0]['index'],input_data) 191 | interpreter.invoke() 192 | 193 | # Retrieve detection results 194 | boxes = interpreter.get_tensor(output_details[boxes_idx]['index'])[0] # Bounding box coordinates of detected objects 195 | classes = interpreter.get_tensor(output_details[classes_idx]['index'])[0] # Class index of detected objects 196 | scores = interpreter.get_tensor(output_details[scores_idx]['index'])[0] # Confidence of detected objects 197 | 198 | # Loop over all detections and draw detection box if confidence is above minimum threshold 199 | for i in range(len(scores)): 200 | if ((scores[i] > min_conf_threshold) and (scores[i] <= 1.0)): 201 | 202 | # Get bounding box coordinates and draw box 203 | # Interpreter can return coordinates that are outside of image dimensions, need to force them to be within image using max() and min() 204 | ymin = int(max(1,(boxes[i][0] * imH))) 205 | xmin = int(max(1,(boxes[i][1] * imW))) 206 | ymax = int(min(imH,(boxes[i][2] * imH))) 207 | xmax = int(min(imW,(boxes[i][3] * imW))) 208 | 209 | cv2.rectangle(frame, (xmin,ymin), (xmax,ymax), (10, 255, 0), 2) 210 | 211 | # Draw label 212 | object_name = labels[int(classes[i])] # Look up object name from "labels" array using class index 213 | label = '%s: %d%%' % (object_name, int(scores[i]*100)) # Example: 'person: 72%' 214 | labelSize, baseLine = cv2.getTextSize(label, cv2.FONT_HERSHEY_SIMPLEX, 0.7, 2) # Get font size 215 | label_ymin = max(ymin, labelSize[1] + 10) # Make sure not to draw label too close to top of window 216 | cv2.rectangle(frame, (xmin, label_ymin-labelSize[1]-10), (xmin+labelSize[0], label_ymin+baseLine-10), (255, 255, 255), cv2.FILLED) # Draw white box to put label text in 217 | cv2.putText(frame, label, (xmin, label_ymin-7), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 0), 2) # Draw label text 218 | 219 | # Draw framerate in corner of frame 220 | cv2.putText(frame,'FPS: {0:.2f}'.format(frame_rate_calc),(30,50),cv2.FONT_HERSHEY_SIMPLEX,1,(255,255,0),2,cv2.LINE_AA) 221 | 222 | # All the results have been drawn on the frame, so it's time to display it. 223 | cv2.imshow('Object detector', frame) 224 | 225 | # Calculate framerate 226 | t2 = cv2.getTickCount() 227 | time1 = (t2-t1)/freq 228 | frame_rate_calc= 1/time1 229 | 230 | # Press 'q' to quit 231 | if cv2.waitKey(1) == ord('q'): 232 | break 233 | 234 | # Clean up 235 | cv2.destroyAllWindows() 236 | videostream.stop() 237 | -------------------------------------------------------------------------------- /Train_TFLite1_Object_Detection_Model.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": { 6 | "id": "view-in-github", 7 | "colab_type": "text" 8 | }, 9 | "source": [ 10 | "\"Open" 11 | ] 12 | }, 13 | { 14 | "cell_type": "markdown", 15 | "metadata": { 16 | "id": "fF8ysCfYKgTP" 17 | }, 18 | "source": [ 19 | "# TensorFlow Lite v1 Object Detection API in Colab\n", 20 | "**Author:** Evan Juras, [EJ Technology Consultants](https://ejtech.io)\n", 21 | "\n", 22 | "**Last updated:** 10/12/22\n", 23 | "\n", 24 | "**GitHub:** [TensorFlow Lite Object Detection](https://github.com/EdjeElectronics/TensorFlow-Lite-Object-Detection-on-Android-and-Raspberry-Pi)\n", 25 | "\n", 26 | "\n", 27 | "# Introduction\n", 28 | "\n", 29 | "This notebook implements [The TensorFlow Object Detection Library](https://github.com/tensorflow/models/tree/master/research/object_detection) for training an SSD-MobileNet model using your own dataset. Note that this notebook uses TensorFlow 1 rather than TensorFlow 2, because TensorFlow 1 works better for quantizing SSD-MobileNet models. If you want to use TensorFlow 2, [clink this link to go to the TensorFlow 2 version of this notebook](https://colab.research.google.com/github/EdjeElectronics/TensorFlow-Lite-Object-Detection-on-Android-and-Raspberry-Pi/blob/master/Train_TFLite2_Object_Detction_Model.ipynb).\n", 30 | "\n", 31 | "*Note: This notebook is not maintained or updated as frequently as the TF2 notebook.*\n", 32 | "\n", 33 | "I made a YouTube video that walks through the TensorFlow 2 notebook step by step, and the process is very similar for this TensorFlow 1 notebook. Please take a look at the video if you are confused about any steps in this notebook.\n", 34 | "\n", 35 | "*Link to video to be added here*\n", 36 | "\n", 37 | "###Working with Colab\n", 38 | "Simply click the play button on sections of code to execute them. As the code executes, any outputs will be displayed in a block beneath the code. Once they're done executing, a green checkmark will appear next to the section to indicate it's finished running." 39 | ] 40 | }, 41 | { 42 | "cell_type": "markdown", 43 | "metadata": { 44 | "id": "l7EOtpvlLeS0" 45 | }, 46 | "source": [ 47 | "# 1. Install TensorFlow Object Detection Dependencies\n", 48 | "First, we'll install the TensorFlow Object Detection API in this Google Colab instance. This requires cloning the [TensorFlow models repository](https://github.com/tensorflow/models) and running a couple installation commands. Click the play button to run the following sections of code.\n", 49 | "\n" 50 | ] 51 | }, 52 | { 53 | "cell_type": "code", 54 | "execution_count": null, 55 | "metadata": { 56 | "id": "QOFtpDlci50l" 57 | }, 58 | "outputs": [], 59 | "source": [ 60 | "# Install CUDA 10.0 (needed for TF v1.15.3)\n", 61 | "!sudo apt-get update\n", 62 | "!sudo apt-get install cuda-10.0" 63 | ] 64 | }, 65 | { 66 | "cell_type": "code", 67 | "execution_count": null, 68 | "metadata": { 69 | "id": "KeoP1s8gi8tL" 70 | }, 71 | "outputs": [], 72 | "source": [ 73 | "# Install cuDNN 7.6.0 (needed for TF v1.15.3)\n", 74 | "# Download and extract cuDNN files\n", 75 | "!wget https://www.dropbox.com/s/k6xqrje655q4aty/cudnn-10.0-linux-x64-v7.6.0.64.tgz\n", 76 | "!tar -xf cudnn-10.0-linux-x64-v7.6.0.64.tgz\n", 77 | "\n", 78 | "# Copy cuDNN libraries to appropriate folders\n", 79 | "!sudo cp cuda/include/cudnn*.h /usr/local/cuda-10.0/include \n", 80 | "!sudo cp -P cuda/lib64/libcudnn* /usr/local/cuda-10.0/lib64 \n", 81 | "!sudo chmod a+r /usr/local/cuda-10.0/include/cudnn*.h /usr/local/cuda-10.0/lib64/libcudnn*\n", 82 | "!sudo cp -P cuda/lib64/libcudnn* /usr/lib64-nvidia" 83 | ] 84 | }, 85 | { 86 | "cell_type": "code", 87 | "execution_count": null, 88 | "metadata": { 89 | "id": "ypWGYdPlLRUN" 90 | }, 91 | "outputs": [], 92 | "source": [ 93 | "# Clone the tensorflow models repository from GitHub\n", 94 | "!git clone --depth 1 https://github.com/tensorflow/models" 95 | ] 96 | }, 97 | { 98 | "cell_type": "code", 99 | "execution_count": null, 100 | "metadata": { 101 | "id": "6QPmVBSlLTzM" 102 | }, 103 | "outputs": [], 104 | "source": [ 105 | "# Install the Object Detection API\n", 106 | "%%bash\n", 107 | "cd models/research/\n", 108 | "protoc object_detection/protos/*.proto --python_out=.\n", 109 | "cp object_detection/packages/tf1/setup.py .\n", 110 | "python -m pip install --use-feature=2020-resolver ." 111 | ] 112 | }, 113 | { 114 | "cell_type": "markdown", 115 | "metadata": { 116 | "id": "kH3fwht2w17a" 117 | }, 118 | "source": [ 119 | "If you get any errors running the following code block, you can ignore them!" 120 | ] 121 | }, 122 | { 123 | "cell_type": "code", 124 | "execution_count": null, 125 | "metadata": { 126 | "id": "kDT0ctlBlXTw" 127 | }, 128 | "outputs": [], 129 | "source": [ 130 | "# Install TensorFlow\n", 131 | "!pip install tensorflow-gpu==1.15.3\n", 132 | "\n", 133 | "# A couple other packages have to be downgraded to work with the TF1 API\n", 134 | "!pip install numpy==1.17.4\n", 135 | "!pip install pycocotools==2.0.0" 136 | ] 137 | }, 138 | { 139 | "cell_type": "code", 140 | "execution_count": null, 141 | "metadata": { 142 | "id": "wh_HPMOqWH9z" 143 | }, 144 | "outputs": [], 145 | "source": [ 146 | "# Run Model Bulider Test file, just to verify everything's working properly\n", 147 | "!python /content/models/research/object_detection/builders/model_builder_tf1_test.py\n" 148 | ] 149 | }, 150 | { 151 | "cell_type": "markdown", 152 | "metadata": { 153 | "id": "IPbU4I7aL9Fl" 154 | }, 155 | "source": [ 156 | "# 2. Upload Image Dataset and Prepare Training Data\n", 157 | "\n", 158 | "In this section, we'll upload our training images and use TFRecord generation scripts to prepare the training data for TensorFlow. we'll upload our images, split them into train, validation, and test folders, and then run scripts for creating TFRecords from our data.\n", 159 | "\n", 160 | "First, on your local PC, zip all your training images and XML files into a single folder called \"images.zip\". The files should be directly inside the zip folder (wihout any additional nested folders) as shown below:\n", 161 | "```\n", 162 | "images.zip\n", 163 | "-- img1.jpg\n", 164 | "-- img1.xml\n", 165 | "-- img2.jpg\n", 166 | "-- img2.xml\n", 167 | "...\n", 168 | "```\n", 169 | "There are two options for moving the image files to this Colab instance: you can upload them directly, or you can copy them from your Google Drive. If you have a slow internet connection or more than 50MB worth of images, I recommend using Google Drive. Otherwise, you can just upload them through Colab. \n", 170 | "\n", 171 | "#### Option 1. Upload through Google Colab\n", 172 | "Upload the \"images.zip\" file to the Google Colab instance by clicking the \"Files\" icon on the left hand side of the browser, and then the \"Upload to session storage\" icon. Select the zip folder to upload it.\n", 173 | "\n", 174 | "![image.png](data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAU4AAADICAYAAACHzQgYAAAYr0lEQVR4Ae2d248V1Z7Hz/P8DfNCMiScNx4MYwgRQ8yMRDESjBqMaPMgAglqpFFDAEVCGkejgSNIokjUNAmJTqNR0QdiD5JgEAh44aaEm4hyG4FWQGGdfNfx164uatelu/auqr0/KynqtqrWqk/V/rAu1bX+5ggQgAAEIJCLwN9yxSYyBCAAAQg4xMlDAAEIQCAnAcSZExjRIQABCCBOngEIQAACOQkgzpzAiA4BCEAAcfIMQAACEMhJAHHmBEZ0CEAAAoiTZwACEIBATgKIMycwokMAAhBAnDwDEIAABHISQJw5gREdAhCAAOLkGYAABCCQkwDizAmM6BCAAAQQJ88ABCAAgZwEEGdOYESHAASqQeDo0aNuxYoVbmBgIDVDiqO4OqaIgDiLoMg5IACBlhPo6elxXV1dbsmSJYnylDQVR3F1TBEBcRZBkXNAAAItJyAhLl68OFGeoTQVV+tFBMRZBEXOAQEIlEIgSZ7NkqYuFHGWcrtJFAIQKIpAnDybKU3lu23Eef36dXfkyBF37ty5ou4H54EABGpCICpPa9MssnoeokgV58qVK113d7c7ffq027dvn+vr68s0bd26NUxnxMvqERs1atQN04QJE9yhQ4fc3r173dixY93DDz/sLly44Ldp38aNG0ecNieAAASqTyCUpzqCmiVNkUgV55w5c3zjq0lTGcoyFdV7ZbdL4rz55pvd8uXL3csvvzw4rV692p06dcodO3bM3XHHHb737MqVK4jTwDGHQIcQCKvnclRab/tIsKSKU+89FV16HE6GJc6pU6dmroqrFEqJczikOQYC9SMQSlMlTU3NlGeqOKuCME2catuUWBVPIU6c+k9g3rx5bsyYMX6aOXOm27Nnj1P7qILm/f39bsqUKb5JQFX/pUuXZpa1Pwn/QAACLSUQlabWNTVTnqniVAbUvll2aFRV37x5s89amjgPHjzoJk2a5J555hm3e/duPy1cuNDddNNNbvv27f4c1k46a9Yst2XLFrdu3To3btw4t2jRInf58uWyEZA+BCAQIRAnTYvSTHmminP+/Pm+yGtVdgksy9Tb22v5L2SuNOM6h7RdIUmcavNcsGCBkxB/+OEHH1fxjx8/7mbMmOFlevXqVS9QpSEZWyn0/Pnz7tq1a4VcAyeBAASKI5AkTUulWfJMFacVd02c6vTJMjVDnEltnEniPHPmjLvrrrtixStR2nkvXbrknn/+eTd69Gg3efJk/x/Et99+izjtKWQOgQoRkIfUjilHSZCNQijPojqtU8WpRKtSVTfBxQHKIk71yFtJMu4ctk2lTHWIPffcc74tVLBVaiVAAALVIaDCnH6bSdK03CqO4uqYIkKqOItIpIhzqEo+XHFaVV1tnHqtysLFixfdmjVr3IEDB7xQd+3a5dTuefLkSYviVq1a5Xvn1dlEgAAEICACqeKUoT///PPSaY1EnMq8OoTU0aOecslRveXjx4/369u2bfPXp7n2a7v2K57W1Q6qUigBAhCAgAikirNKL8APt8Rpt1r/CYSvI82ePdt98803ttuXOnfs2OGmT5/u2zklTfXCnzhxYjAOCxCAAARSxfn666879ayX/SeX3CoIQAACVSGQKs6qZJR8QAACEKgKAcRZlTtBPiAAgdoQQJy1uVVkFAIQqAoBxFmVO0E+IACB2hBAnLW5VWQUAhCoCgHEWZU7QT4gAIHaEECctblVZBQCEKgKAcRZlTtBPiAAgdoQQJy1uVVkFAIQqAoBxFmVO0E+IACB2hBAnLW5VWQUAhCoCoHai/Psv/+Hq8pUlZtKPiAAgeYSQJwFire5t4qzQwACVSGAOBFnVZ5F8gGB2hBAnIizNg8rGYVAVQggTsRZlWeRfECgNgTaUpytoB/XIdWKdEkDAhAonwDiHOY9QJzDBMdhEGgDAohzmDcRcQ4THIdBoA0IIM5h3kTEOUxwHAaBNiDQEeLUWOrd3d2uq6srcdLAdFkD4sxKingQaD8CHSFOjdKZJk3bn1WeiLP9fgxcEQSyEugIcZoUk6BYHM2zyLMZ4ty5c6dbsmRJQ8lnyVfSNTZrX13z3SwenLf9CSDOP+9xKM4s8myGOLOUjKsoz7rmu/1/3lxhswggzgbiTJNnM8QZlfdw19Weq1Jgq8Jw8xk9rtX5bhUf0mk/Aojzz3sa/RHbeqMSXpXFqbyryt+qYKyyzBcvXux6enr8FFdSbWW+W8WHdNqPAOL8854m/ejjbnszxRmXXp5tdi15jhlJXEsvy/zo0aODSZ0+fTq2PXcwAgsQqCgBxJlwY0wEcVEQ519UjFPavK+v74aOt97e3hvk+deZWYJANQkgzoT7YiKIi4I4/6JinJLmqpYrfPrpp07v1VoYGBhwc+bMGSJP28ccAlUl0PHi3Lp16w2lILtZJgJbD+dVFmeYz1YsG6ekeShLlTLDoJJoeGy4j2UIVJFAR4tT0rQfrEpC0WD7otu1XhVxqs1Q4lmxYoVbtWqVL9GpFGcdMHF5L3qbcWo0X7ly5ZAkxVrswxB2FIXbWYZAFQl0rDhDadoPPvpjtu1xN64K4pSALI/qrdakdb3Wo0nybEWwPDSaqxMoDJKk8ifBW9DrU3a8bWMOgaoS6EhxxknTfrShPG1b3M0rW5zWIy1ZWk+1tkmmc+fO9RKqgjhVGg5DWC3Xq0cqKduUxDs8B8sQKJtAZnHqx6mqoD3cmmvdfrRlXUgWgVmeLY96N1PbQoHacvjeZvQ4O17zLOmG8bMsJ6UXPd4EFJbmbJudpwriDPOnEma0fTO8Lit1httYhkAVCWQSp+RopRi1V+kHqrl+oNoeNvy3+iKzCMxEEubN8hzus20WL9xn22yeJV2Lm3WelF70HHFtmJKUrsGmVv2nZvmOm+tZsSBphtVz225ze6ZsnTkEqkogVZx60CVHvTIS/SFqXe1V2p/0g2jmxWcRmP2g4/Ix3H1Z0o1LL2lbUl6ix8WJMxqnVeuW70ZzCV3PSijRaN4kezs+uo91CFSNQKo4rQobtv2FF2HVq0b7w7jNWM4isKQf5HD3ZUk37/WGPcuWr7i5JGPNDWFV2NIzCcW9KWBxipyn5ds6q6Jtmta2qbnVaNRmS4BA1QmkitPazZJKlPpxJ7VdNRNCFoGZfPLmI+m4LOnmTU//CVnPuKUdN5doVILTPskolKe2a5v2hdvz5iVP/Kz5jruWcJsErHMRIFB1AoWJM+xUaeVFZxFY9C9Twh9r2rJ+zHEhS7pxxxW5LXwdyUpvdj1l1QCKvD7OBYGqEkgVZ9aqequqhVGQWQSmqmtaddKEE851jI6NC1nSjTuu6G0qVYq9tXnqP7BGeS46bc4HgU4lkCpOVdFVYlMbVLTqp3W1X2l/UlW+mXDLElhZ6TaTJeeGAASyEUgVp06jEozJUyUatXtqbg361uaWLcliY5UlsLLSLZYeZ4MABIZDIJM4dWJ1Oth7dladVfVQ202qWm51KEtgZaXbar6kBwEI3EggszjDQ6OCLFOecQIra1vIiGUIQKB9CQxLnHE4ypJnWZKMSzeOC9sgAIH2I1CYOIUmlGerOoviBFbWtvZ7PLgiCEAgjkCh4lQCkmcr3yEsS5Jx6cYBZhsEINB+BAoXZ6sRxQmsrG2tvnbSgwAEyiFQe3GWg41UIQCBTiaAODv57nPtEIDAsAjUQpwHDx50TDDgGeAZGOkzMCxLxhxUC3HG5JtNEIAABEojgDhLQ0/CEIBAXQkgzrreOfINAQiURgBxloaehCEAgboSQJx1vXPkGwIQKI0A4iwNPQlDAAJ1JYA463rnyDcEIFAaAcRZGnoShgAE6koAcdb1zpFvCECgNAKIszT0JAwBCNSVAOKs650j3xCAQGkEEGdp6EkYAhCoKwHEWdc7R74hAIHSCHSsOPWVeg1rHJ16e3tLuxkkDAEI1INAR4pTw3vYEMdxc40ZT4AABCDQiEBHinPfvn2J4oyTabhNpVQCBCDQuQQQZ1fXoETnz58/uByKMm5Z8iVAAAKdSQBx/ilOq55rHifK6DbE2Zk/GK4aAiKAOLu6nEnTHoks8kScRos5BDqPQMeLMypNewTS5Ik4jRRzCHQegY4W55IlS9z+/fudetnDoHVt1/5oFd3WEWdIjGUIdBaBjhanSbCnp2fIXde67Ws0TxLn77//7nbs2OF+/PFHf97ffvvNbdu2zf3yyy9D0mEFAhCoJwHE2dXlihbnhQsX3MyZM92mTZv8U3H8+HE3ZcoU9+WXX9bzKSHXEIDAEAKIswniHEKYFQhAoO0IIM6uLjd37twhf3qp9UZVdNueVFVvu6eEC4IABIYQ6Ehxpv3JpckxaR7tUBpClRUIQKCtCXSkOHVH9ZGPlStX+vZNtXFmnXTMzp072/qh4OIgAIFkAh0rzmQs7IUABCDQmADibMyGPRCAAARiCSDOWCxshAAEINCYQC5xDgwMuL6+vtizabv2EyAAAQi0O4HM4pQU7U8Qo6/iaF090NqPPNv9keH6IACBTOIMpane6Lig7cgzjgzbIACBdiOQKs4s0jQoyNNIMIcABNqZQKI480jTICFPI8EcAhBoVwKp4ly8eLGvgjeqokfBmDh1HO2dUTqsQwAC7UAgUZy6QMkvqzyRZvUeiXPnzrmpU6e67du3F5o5ne+xxx5zv/76a6Hn5WQQqAOBVHHqIrLIs27SVH6jY6prvd3GVa+COA8dOuTuvvtupzkBAu1AIJM4daGhPBu9jlSX6nnaRz4aDadRxxuOOOt418hz1QlkFqcuRPIs8wX4n376qRCe9t5p0tePkvZlGVf9+++/dzNmzHCjR492kyZNcu+//767du2az7++CL9mzRo3btw4N2bMGPf444+7M2fO+H0qlT300EPuzTffdOPHj/f7ly9f7o4cOeLmzZvnz3f77bf7L8zrAIlReX377bedtiu97u5uv932h1X169evu08++WQwrs556tQpn3bSP8qfpT99+nT36quvDqmqnzhxwu/X9ei61q1b5/Ql/I0bN7pRo0YNTlpXSGKQlA/2QaAKBHKJs+wMr169OtOPPC2fIxWnRBUtdYdp2hfgN2zY4K5eveq+/vprN3ny5MEvwK9du9Y98cQT7vz58+7KlSvupZdecsuWLfOikTgnTpzo1q9f7y5duuQOHjzoj73zzju9LC9fvuybEyRlDcVhJUrJVeeTBCU4O5/ttzbO/v5+L3R9lV7yeuONN4YIMLwOW1YeFy5c6JSG0jx58qRPw9o4w+uVLLVfcv3ss8/8KeKq6kkMLF3mEKgqgdqJ8+mnnx6xPJstTsnqgQcecHv27PH3XTLZu3evO3bsmFOJT/IJO1UktXvuuceLz0qcVgJVfAlLk5YVQhEpLZVQJVgLGqJDpUyNeRSKU2lKdlu2bLGoPs6DDz7oDh8+PLgtuqD0lD+Vei1oDCUTp65P0tbcgkrl+g9BIcyv1tMY2DmYQ6CqBGonTpX2RirPZotTAlFVVVVW/RmqOqJUurOgJg+rqls1VqKT5CQZXaOWLVgnlq2HIlI8xdc2C5KuZKptoTht2dK0+YQJE4Ycb+exucQuuYaDzUV71dVurJKuqup2XmvSCPNr50xiYHGYQ6CqBGopzpHKs9nitJv9888/u48++sgP3DZt2jRfYpNUVRJTVf3s2bM+qiRUpDhV0lSJN06c9913n9u9e7dlMdM8TZy6zvvvv983IahpQiGUfVScaQwyZYpIECiRAOLs6vIlNsk4z5TUxikhanhgtQ0qaL5gwQLfUWLVZcnIgqq9RYpTbar33nvvDVV1tZk++uijviPJ0la12TqtbFt0LvElVdW1X+yslGzNC41KnGkMoumzDoGqEailOKteVVdboN5b3Lx5s5eS2jZV0tO6SmSLFi1yS5cu9SVOtU2qI2Uk4tSxagNVJ43aGlWaValWJTurnpuoP/zwQ3fbbbf54T/++OMPL3jFN+nFPaDRziF1LD3yyCODbZxa1/V+8MEHvkPr448/9s0UJk41HUi8yoM6t9IYxOWBbRCoEoHaiXOk0hT8VlTVv/rqq8HXkfRa0TvvvDPYeRJ9tUf7rIQYLb0pv2G1V+th1VfCU3um2kztdSS9T2sijIpTpUu9GqVXpNQWKWkrr2khmufo60gqYSt9tXHqPwXl+dlnn/XXbG2+2mcdRtHzhQzS8sJ+CJRNoHbizPLOYRrUVogzLQ9F7ZcYVU2WTEcSdLw6iaxjx+ZpHUcjSZNjIVBXArUSZ1VegJeokto4W/kwFCXOVuaZtCBQdwK1EmdRsNP+5DJLJ1FVxlVHnEU9FZwHAtkJdKQ4hUfvVjKuevYHhZgQgMBfBDpWnH8hYAkCEIBAPgKIMx8vYkMAAhBwiJOHAAIQgEBOAogzJzCiQwACEECcPAMQgAAEchJAnDmBER0CEIAA4uQZgAAEIJCTAOLMCYzoEIAABDpenPoLoP379zeceEQgAAEIRAl0tDhtSOOkP7EsasRLfVLNhpqI3oQqrmuwOX05SR/7uOWWW/x3PLVNQd8M0N/q21AeVcw/eYJAMwl4cZ564t+cTc1MrGrn1p9cNpJmT0+PkzS1vwh55hGn4tr3OUfKzD4abKNLxp0v+tk6fTPzySefdC+++KIfG0ml8ilTpvjvaer4TZs2+a/a6/ufWc4flybbIFBnAh0tTskxSZy6sUXJs07i1MeQ7cPDYhD9pmf4wCPOkAbLnUIAcTYYMkNStTAceUY/1Bv98K+GsXjllVfc2LFj/aRhevWldZX+7FuYmku4Co3ia58+FPzuu+/6cdg1rvrs2bOdxjmPfmMz2lRgQrT0VMrduXPnkO9yatt3333nS8CWF5VedS59ADn8hmd4/l27dvmPM+vcqvKHo3AaV+YQqCsBxJlBnLq5Js/e3t7Uex0daiI6DrlE98ILL/iqsEa/1MeZNRaQVaejVfW0+JKm5KQhLJT2W2+95ebOnetlm6VEGK2qm1BNlNF1E6fOHXf+AwcO+BKrxj7SMBkarkOjZGpQNwIE2oEA4mwgzu7ubtfX1zdkkozCkmijB0AlvbTBzTQKpUajtKCxztWuqPbFqDh1vkbxNTCcxv8Jx0pX26OGopCQ48Rmadq8SHGqw0j/KWh4ZAsqLc+aNcuPb2TbmEOgzgQQZwNxprV9Jt10iS9pHHLtt+pxOLeqblScSfFVmtWYQ42qwq0Wp6UXXpct6zoIEGgHAoizJHFGxRo+THHibBRf1egqilPjyRMg0K4EEGcTxJlWVdc7kBoRMhy3SKNP2nuRUXEmxVc1OFpVV7upOnkuXrzY8qq62mOXLVvmJy1bCJdtG3MI1JUA4myCOKOdQ9FxyG3/vHnz3OnTp51Et379evfaa685jXWuave0adN8r7jaPNPiJ3UOScYac109+KpGS9B6kX3Pnj2Dot6wYYNTm67aRiW4aGdQdD3sHIo7v3rU1duuceR1PUpP7cMab54AgXYggDibIE49GGmvI6k0KKHpdSQbi1yCUpDgVGrTq0Xvvfee35YUX7Kz15F0roULF/qOIX+gc04iu/XWW32Hld7RXLt2rXvqqad8R5TiSGga133ixInu8OHDucSp46Pnl0y/+OIL/9K82jdVuu7v7x8UteWLOQTqSgBxNkmcdX0gyDcEIJBOAHEizvSnhBgQgMAQAogTcQ55IFiBAATSCaSK84+zR9yV7/7vhun6r/+ffvaKx0j6W/VG73HqwyAECECgswkkilPStK8mRednXrzZ1V2eWT4rFxWoXvMhQAACnU0gUZwXNy9rKE6J9PTzf3fnXv3v2OnC/y6oBVl9Mk3vSWaZFJcAAQhAIJM4z/zPf7pz//ivzJPiS6yX974PYQhAAAJtRyCTOK8c6s914YovcarESoAABCDQbgQQZ7vdUa4HAhBoOgHE2XTEJAABCLQbAcTZbneU64EABJpOAHE2HTEJQAAC7UYAcbbbHeV6IACBphNAnE1HTAIQgEC7EcgkzoH+f9zwJ5dxf4Zp2xSf15Ha7VHheiAAASOQKM6kP7mUGNMmHU+AAAQg0G4EEsWpi/Uf+TjU7/RSe54Jabbbo8L1QAACRiBVnBaROQQgAAEI/IsA4uRJgAAEIJCTAOLMCYzoEIAABBAnzwAEIACBnAQQZ05gRIcABCCAOHkGIAABCOQkgDhzAiM6BCAAAcTJMwABCEAgJwHEmRMY0SEAAQggTp4BCEAAAjkJIM6cwIgOAQhAAHHyDEAAAhDISQBx5gRGdAhAAAJenGCAAAQgAIHsBBBndlbEhAAEIOAJIE4eBAhAAAI5CSDOnMCIDgEIQABx8gxAAAIQyEkAceYERnQIQAACiJNnAAIQgEBOAogzJzCiQwACEECcPAMQgAAEchJAnDmBER0CEIAA4uQZgAAEIJCTAOLMCYzoEIAABBAnzwAEIACBnAQQZ05gRIcABCCAOHkGIAABCOQk8E8+8JrBrLqvUwAAAABJRU5ErkJggg==)\n", 175 | "\n", 176 | "Once it's uploaded, we'll run a few commands to unzip it and set up our image directories. These directories are created in the /content folder in this instance's filesystem. You can browse the filesystem by clicking the \"Files\" icon on the left.\n" 177 | ] 178 | }, 179 | { 180 | "cell_type": "markdown", 181 | "metadata": { 182 | "id": "Wpi58u0-V_jN" 183 | }, 184 | "source": [ 185 | "#### Option 2. Copy from Google Drive\n", 186 | "You can also upload your images to your personal Google Drive, mount the drive on this Colab session, and copy them over to the Colab filesystem. This option works well if you want to upload the images beforehand so you don't have to wait for them to upload each time you restart this Colab.\n", 187 | "\n", 188 | "First, upload the \"images.zip\" file to your Google Drive, and make note of the folder you uploaded them to. Replace `MyDrive/path/to/images.zip` with the path to your zip file. (For example, I uploaded the zip file to folder called \"cat-toys1\", so I would use `MyDrive/cat-toys1/images.zip` for the path). Then, run the following block of code to mount your Google Drive to this Colab session and copy the folder to this filesystem." 189 | ] 190 | }, 191 | { 192 | "cell_type": "code", 193 | "execution_count": null, 194 | "metadata": { 195 | "id": "tLgAPsQsfTLs" 196 | }, 197 | "outputs": [], 198 | "source": [ 199 | "from google.colab import drive\n", 200 | "drive.mount('/content/gdrive')\n", 201 | "\n", 202 | "!cp /content/gdrive/MyDrive/path/to/images.zip /content" 203 | ] 204 | }, 205 | { 206 | "cell_type": "markdown", 207 | "metadata": { 208 | "id": "DLIu4GdjxgWu" 209 | }, 210 | "source": [ 211 | "#### Option 3. Use my bird, squirrel, raccoon dataset\n", 212 | "I've uploaded a dataset containing 800 labeled images of birds, squirrels, and raccoons. You can use this dataset if you just want to work through the process of training a TFLite model on custom data. Run the following code block to download the dataset." 213 | ] 214 | }, 215 | { 216 | "cell_type": "code", 217 | "execution_count": null, 218 | "metadata": { 219 | "id": "bH6huqCBxhFb" 220 | }, 221 | "outputs": [], 222 | "source": [ 223 | "!wget -O /content/images.zip https://www.dropbox.com/s/en33x280e4z3wbt/BSR2.zip?dl=0" 224 | ] 225 | }, 226 | { 227 | "cell_type": "markdown", 228 | "metadata": { 229 | "id": "U-eXEQICx2ug" 230 | }, 231 | "source": [ 232 | "## Split images into train, validation, and test folders\n", 233 | "At this point, whether you used Option 1, 2, or 3, you should be able to click the folder icon on the left and see your \"images.zip\" file in the list of files. Now that the dataset is uploaded, let's unzip it and create some folders to hold the images. These directories are created in the /content folder in this instance's filesystem. You can browse the filesystem by clicking the \"Files\" icon on the left." 234 | ] 235 | }, 236 | { 237 | "cell_type": "code", 238 | "execution_count": null, 239 | "metadata": { 240 | "id": "3n-E68MJbU2I" 241 | }, 242 | "outputs": [], 243 | "source": [ 244 | "!mkdir /content/images\n", 245 | "!unzip -q images.zip -d /content/images/all\n", 246 | "!mkdir /content/images/train; mkdir /content/images/validation; mkdir /content/images/test\n" 247 | ] 248 | }, 249 | { 250 | "cell_type": "markdown", 251 | "metadata": { 252 | "id": "n-6RIcrwbQMh" 253 | }, 254 | "source": [ 255 | "Next, we'll split the images into train, validation, and test sets. Here's what each set is used for:\n", 256 | "\n", 257 | "\n", 258 | "* **Train**: These are the actual images used to train the model. In each step of training, a batch of images from the \"train\" set is passed into the neural network. The network predicts classes and locations of objects in the images. The training algorithm calculates the loss (i.e. how \"wrong\" the predictions were) and adjusts the network weights through backpropagation.\n", 259 | "\n", 260 | "\n", 261 | "* **Validation**: Images from the \"validation\" set can be used by the training algorithm to check the progress of training and adjust hyperparameters (like learning rate). Unlike \"train\" images, these images are only used periodically during training (i.e. once every certain number of training steps).\n", 262 | "\n", 263 | "\n", 264 | "* **Test**: These images are never seen by the neural network during training. They are intended to be used by a human to perform final testing of the model to check how accurate the model is.\n", 265 | "\n", 266 | "I wrote a Python script to randomly move 80% of the images to the \"train\" folder, 10% to the \"validation\" folder, and 10% to the \"test\" folder. Click play on the following block to download the script and execute it." 267 | ] 268 | }, 269 | { 270 | "cell_type": "code", 271 | "execution_count": null, 272 | "metadata": { 273 | "id": "_8V38Uk2yBUZ" 274 | }, 275 | "outputs": [], 276 | "source": [ 277 | "!wget https://raw.githubusercontent.com/EdjeElectronics/TensorFlow-Lite-Object-Detection-on-Android-and-Raspberry-Pi/master/util_scripts/train_val_test_split.py\n", 278 | "!python train_val_test_split.py" 279 | ] 280 | }, 281 | { 282 | "cell_type": "markdown", 283 | "metadata": { 284 | "id": "p--K1PJXEgNo" 285 | }, 286 | "source": [ 287 | "## Create TFRecords\n", 288 | "Finally, we need to convert the images into a data file format called TFRecords, which are used by TensorFlow for training. We'll use Python scripts to automatically convert the data into TFRecord format. Before running them, we need to define a labelmap for our classes. \n", 289 | "\n", 290 | "The code section below will create a \"labelmap.txt\" file that contains a list of classes. Replace the `class1`, `class2`, `class3` text with your own classes (for example, `bird`, `squirrel`, `raccoon`), adding a new line for each class. Then, click play to execute the code." 291 | ] 292 | }, 293 | { 294 | "cell_type": "code", 295 | "execution_count": null, 296 | "metadata": { 297 | "id": "_DE_r4MKY7ln" 298 | }, 299 | "outputs": [], 300 | "source": [ 301 | "### This creates a a \"labelmap.txt\" file with a list of classes the object detection model will detect.\n", 302 | "%%bash\n", 303 | "cat <> /content/labelmap.txt\n", 304 | "class1\n", 305 | "class2\n", 306 | "class3\n", 307 | "EOF" 308 | ] 309 | }, 310 | { 311 | "cell_type": "markdown", 312 | "metadata": { 313 | "id": "5pa2VYhTIT1l" 314 | }, 315 | "source": [ 316 | "Download and run the data conversion scripts by clicking play on the following two sections of code. They will create TFRecord files for the train and validation datasets, as well as a `labelmap.pbtxt` file which contains the labelmap in a different format." 317 | ] 318 | }, 319 | { 320 | "cell_type": "code", 321 | "execution_count": null, 322 | "metadata": { 323 | "id": "v3KHhgrpHved" 324 | }, 325 | "outputs": [], 326 | "source": [ 327 | "# Download data conversion scripts\n", 328 | "! wget https://raw.githubusercontent.com/EdjeElectronics/TensorFlow-Lite-Object-Detection-on-Android-and-Raspberry-Pi/master/util_scripts/create_csv.py\n", 329 | "! wget https://raw.githubusercontent.com/EdjeElectronics/TensorFlow-Lite-Object-Detection-on-Android-and-Raspberry-Pi/master/util_scripts/create_tfrecord.py" 330 | ] 331 | }, 332 | { 333 | "cell_type": "code", 334 | "execution_count": null, 335 | "metadata": { 336 | "id": "5tdDbTmHYwu-" 337 | }, 338 | "outputs": [], 339 | "source": [ 340 | "# Create CSV data files and TFRecord files\n", 341 | "!python3 create_csv.py\n", 342 | "!python3 create_tfrecord.py --csv_input=images/train_labels.csv --labelmap=labelmap.txt --image_dir=images/train --output_path=train.tfrecord\n", 343 | "!python3 create_tfrecord.py --csv_input=images/validation_labels.csv --labelmap=labelmap.txt --image_dir=images/validation --output_path=val.tfrecord" 344 | ] 345 | }, 346 | { 347 | "cell_type": "markdown", 348 | "metadata": { 349 | "id": "1XcSBULRzNZ_" 350 | }, 351 | "source": [ 352 | "We'll store the locations of the TFRecord and labelmap files as variables so we can reference them later in this Colab session." 353 | ] 354 | }, 355 | { 356 | "cell_type": "code", 357 | "execution_count": null, 358 | "metadata": { 359 | "id": "YUd2wtfrqedy" 360 | }, 361 | "outputs": [], 362 | "source": [ 363 | "train_record_fname = '/content/train.tfrecord'\n", 364 | "val_record_fname = '/content/val.tfrecord'\n", 365 | "label_map_pbtxt_fname = '/content/labelmap.pbtxt'" 366 | ] 367 | }, 368 | { 369 | "cell_type": "markdown", 370 | "metadata": { 371 | "id": "I2MAcgJ53STW" 372 | }, 373 | "source": [ 374 | "# 3. Set Up Training Configuration \n", 375 | "\n", 376 | "In this section, we'll set up an SSD-MobileNet model training configuration. We'll specifiy which pretrained TensorFlow model we want to use from the [TensorFlow 1 Object Detection Model Zoo](https://github.com/tensorflow/models/blob/master/research/object_detection/g3doc/tf1_detection_zoo.md). Each model also comes with a configuration file that points to file locations, sets training parameters (such as learning rate and total number of training steps), and more. We'll modify the configuration file for our custom training job.\n", 377 | "\n", 378 | "The first section of code lists out some models availabe in the TF1 Model Zoo and defines some filenames that will be used later to download the model and config file. This makes it easy to manage which model you're using and to add other models to the list later. \n", 379 | "\n", 380 | "Set the \"chosen_model\" variable to match the name of the model you'd like to train with. It's currently set to use the \"ssd-mobilenet-v1-quantized\" model. Then, click play on the next three sections to specify and download the pretrained model file and configuration file.\n" 381 | ] 382 | }, 383 | { 384 | "cell_type": "code", 385 | "execution_count": null, 386 | "metadata": { 387 | "id": "gN0EUEa3e5Un" 388 | }, 389 | "outputs": [], 390 | "source": [ 391 | "# Change the \"chosen_model\" variable to select one of the models listed below (from the TF1 object detection zoo)\n", 392 | "\n", 393 | "chosen_model = 'ssd-mobilenet-v2-quantized'\n", 394 | "\n", 395 | "MODELS_CONFIG = {\n", 396 | " 'ssd-mobilenet-v1-quantized': {\n", 397 | " 'model_name': 'ssd_mobilenet_v1_quantized_coco',\n", 398 | " 'base_pipeline_file': 'ssd_mobilenet_v1_quantized_300x300_coco14_sync.config',\n", 399 | " 'pretrained_checkpoint': 'ssd_mobilenet_v1_quantized_300x300_coco14_sync_2018_07_18.tar.gz',\n", 400 | " },\n", 401 | " 'ssd-mobilenet-v2-quantized': {\n", 402 | " 'model_name': 'ssd_mobilenet_v2_quantized_coco',\n", 403 | " 'base_pipeline_file': 'ssd_mobilenet_v2_quantized_300x300_coco.config',\n", 404 | " 'pretrained_checkpoint': 'ssd_mobilenet_v2_quantized_300x300_coco_2019_01_03.tar.gz',\n", 405 | " }\n", 406 | "}\n", 407 | "\n", 408 | "model_name = MODELS_CONFIG[chosen_model]['model_name']\n", 409 | "pretrained_checkpoint = MODELS_CONFIG[chosen_model]['pretrained_checkpoint']\n", 410 | "base_pipeline_file = MODELS_CONFIG[chosen_model]['base_pipeline_file']" 411 | ] 412 | }, 413 | { 414 | "cell_type": "code", 415 | "execution_count": null, 416 | "metadata": { 417 | "id": "kG4TmJUVrYQ7" 418 | }, 419 | "outputs": [], 420 | "source": [ 421 | "# Create \"mymodel\" folder for holding pre-trained weights and configuration files\n", 422 | "%mkdir /content/models/mymodel/\n", 423 | "%cd /content/models/mymodel/\n", 424 | "\n", 425 | "# Download pretrained model file from TensorFlow Model Zoo\n", 426 | "import tarfile\n", 427 | "download_tar = 'http://download.tensorflow.org/models/object_detection/' + pretrained_checkpoint\n", 428 | "\n", 429 | "!wget {download_tar}\n", 430 | "tar = tarfile.open(pretrained_checkpoint)\n", 431 | "tar.extractall()\n", 432 | "tar.close()\n", 433 | "\n", 434 | "# Download base training configuration file\n", 435 | "download_config = 'https://raw.githubusercontent.com/tensorflow/models/master/research/object_detection/samples/configs/' + base_pipeline_file\n", 436 | "!wget {download_config}" 437 | ] 438 | }, 439 | { 440 | "cell_type": "markdown", 441 | "metadata": { 442 | "id": "3jOYxQ20wXVr" 443 | }, 444 | "source": [ 445 | "Now that we've downloaded our model and config file, we need to modify the configuration file with some high-level training parameters. The following variables are used to control training steps:\n", 446 | "\n", 447 | "* **num_steps**: The total amount of steps to use for training the model. A good number to start with is 40,000 steps. You can use more steps if you notice the loss metrics are still decreasing by the time training finishes. The more steps, the longer the training. Training can also be stopped early if loss flattens out before reaching the specified number of steps.\n", 448 | "* **batch_size**: The number of images to use per training step. A larger batch size allows a model to be trained in fewer steps, but the size is limited by the GPU memory available for training. For the GPUs used in Colab instances, 16 is typically a good number.\n", 449 | "\n", 450 | "* **quant_delay_steps**: (Only used for quantization-aware training) After this many steps, the training algorithm will insert \"fake\" quantization nodes in the network to simulate the effects of quantization. A good starting point is half the total number of training steps. More information is available [here](https://neuralet.com/article/quantization-of-tensorflow-object-detection-api-models/).\n", 451 | "\n", 452 | "Other training information, like the location of the pretrained model file, the config file, and total number of classes are also assigned in this step.\n" 453 | ] 454 | }, 455 | { 456 | "cell_type": "code", 457 | "execution_count": null, 458 | "metadata": { 459 | "id": "IKiXz33QwXyP" 460 | }, 461 | "outputs": [], 462 | "source": [ 463 | "# Set training parameters for the model\n", 464 | "num_steps = 30000\n", 465 | "batch_size = 16\n", 466 | "quant_delay_steps = 15000" 467 | ] 468 | }, 469 | { 470 | "cell_type": "code", 471 | "execution_count": null, 472 | "metadata": { 473 | "id": "b_ki9jOqxn7V" 474 | }, 475 | "outputs": [], 476 | "source": [ 477 | "# Set file locations and get number of classes for config file\n", 478 | "pipeline_fname = '/content/models/mymodel/' + base_pipeline_file\n", 479 | "fine_tune_checkpoint = '/content/models/mymodel/' + model_name + '/model.ckpt'\n", 480 | "\n", 481 | "def get_num_classes(pbtxt_fname):\n", 482 | " from object_detection.utils import label_map_util\n", 483 | " label_map = label_map_util.load_labelmap(pbtxt_fname)\n", 484 | " categories = label_map_util.convert_label_map_to_categories(\n", 485 | " label_map, max_num_classes=90, use_display_name=True)\n", 486 | " category_index = label_map_util.create_category_index(categories)\n", 487 | " return len(category_index.keys())\n", 488 | "num_classes = get_num_classes(label_map_pbtxt_fname)\n", 489 | "print('Total classes:', num_classes)\n" 490 | ] 491 | }, 492 | { 493 | "cell_type": "markdown", 494 | "metadata": { 495 | "id": "xxpOXUTx0E-n" 496 | }, 497 | "source": [ 498 | "Finally, we'll rewrite the config file to use the training parameters we just specified. The following section of code will automatically replace the necessary parameters in the downloaded .config file and save it as our custom \"pipeline_file.config\" file." 499 | ] 500 | }, 501 | { 502 | "cell_type": "code", 503 | "execution_count": null, 504 | "metadata": { 505 | "id": "5eA5ht3_yukT" 506 | }, 507 | "outputs": [], 508 | "source": [ 509 | "# Write custom configuration file by slotting our dataset, model checkpoint, and training parameters into the base pipeline file\n", 510 | "# TO DO: change that description\n", 511 | "\n", 512 | "import re\n", 513 | "\n", 514 | "%cd /content/models/mymodel\n", 515 | "print('writing custom configuration file')\n", 516 | "\n", 517 | "with open(pipeline_fname) as f:\n", 518 | " s = f.read()\n", 519 | "with open('pipeline_file.config', 'w') as f:\n", 520 | " \n", 521 | " # fine_tune_checkpoint\n", 522 | " s = re.sub('fine_tune_checkpoint: \".*?\"',\n", 523 | " 'fine_tune_checkpoint: \"{}\"'.format(fine_tune_checkpoint), s)\n", 524 | " \n", 525 | " # tfrecord files train and test.\n", 526 | " s = re.sub(\n", 527 | " '(input_path: \".*?)(PATH_TO_BE_CONFIGURED/mscoco_train.*?\")', 'input_path: \"{}\"'.format(train_record_fname), s)\n", 528 | " s = re.sub(\n", 529 | " '(input_path: \".*?)(PATH_TO_BE_CONFIGURED/mscoco_val)(.*?\")', 'input_path: \"{}\"'.format(val_record_fname), s)\n", 530 | "\n", 531 | " # label_map_path\n", 532 | " s = re.sub(\n", 533 | " 'label_map_path: \".*?\"', 'label_map_path: \"{}\"'.format(label_map_pbtxt_fname), s)\n", 534 | "\n", 535 | " # Set training steps, num_steps\n", 536 | " s = re.sub('num_steps: [0-9]+',\n", 537 | " 'num_steps: {}'.format(num_steps), s)\n", 538 | "\n", 539 | " # Set training batch_size.\n", 540 | " s = re.sub('batch_size: [0-9]+',\n", 541 | " 'batch_size: {}'.format(batch_size), s)\n", 542 | " \n", 543 | " # Set number of classes num_classes.\n", 544 | " s = re.sub('num_classes: [0-9]+',\n", 545 | " 'num_classes: {}'.format(num_classes), s)\n", 546 | " \n", 547 | " # Fine-tune checkpoint type\n", 548 | " s = re.sub(\n", 549 | " 'fine_tune_checkpoint_type: \"classification\"', 'fine_tune_checkpoint_type: \"{}\"'.format('detection'), s)\n", 550 | " \n", 551 | " # Quantization delay steps\n", 552 | " s = re.sub(\n", 553 | " 'delay: 48000', 'delay: {}'.format(quant_delay_steps), s)\n", 554 | " \n", 555 | " f.write(s)\n", 556 | "\n", 557 | "%cd /content\n" 558 | ] 559 | }, 560 | { 561 | "cell_type": "code", 562 | "execution_count": null, 563 | "metadata": { 564 | "id": "HEsOLOMHzBqF" 565 | }, 566 | "outputs": [], 567 | "source": [ 568 | "# Display the custom configuration file's contents\n", 569 | "!cat /content/models/mymodel/pipeline_file.config" 570 | ] 571 | }, 572 | { 573 | "cell_type": "code", 574 | "execution_count": null, 575 | "metadata": { 576 | "id": "GMlaN3rs3zLe" 577 | }, 578 | "outputs": [], 579 | "source": [ 580 | "# Set the path to the custom config file, and the directory to store training checkpoints in\n", 581 | "pipeline_file = '/content/models/mymodel/pipeline_file.config'\n", 582 | "model_dir = '/content/training/'" 583 | ] 584 | }, 585 | { 586 | "cell_type": "markdown", 587 | "metadata": { 588 | "id": "bLe247WqxPHw" 589 | }, 590 | "source": [ 591 | "The next block modifies the training script to save checkpoints once every 1000 training steps. Change `num_eval_steps` if you'd like to save checkpoints more or less frequently." 592 | ] 593 | }, 594 | { 595 | "cell_type": "code", 596 | "execution_count": null, 597 | "metadata": { 598 | "id": "oi_wWkJOnc-D" 599 | }, 600 | "outputs": [], 601 | "source": [ 602 | "# Modify model_main.py to set evaluation and checkpoint interval\n", 603 | "num_eval_steps = 1000\n", 604 | "%cd /content/models/research/object_detection\n", 605 | "print('Modifying model_main.py')\n", 606 | "\n", 607 | "\n", 608 | "with open('model_main.py', 'r') as f:\n", 609 | " data = f.read()\n", 610 | "\n", 611 | " # Set eval steps\n", 612 | " data = data.replace('config = tf_estimator.RunConfig(model_dir=FLAGS.model_dir)', \n", 613 | " 'config = tf.estimator.RunConfig(model_dir=FLAGS.model_dir, save_checkpoints_steps={})'.format(num_eval_steps))\n", 614 | " \n", 615 | " f.close()\n", 616 | "\n", 617 | "!rm /content/models/research/object_detection/model_main.py\n", 618 | "\n", 619 | "with open('model_main.py', 'w') as f:\n", 620 | " f.write(data)\n", 621 | "\n", 622 | "%cd /content" 623 | ] 624 | }, 625 | { 626 | "cell_type": "code", 627 | "execution_count": null, 628 | "metadata": { 629 | "id": "7wD0tda5oqIR" 630 | }, 631 | "outputs": [], 632 | "source": [ 633 | "!cat /content/models/research/object_detection/model_main.py" 634 | ] 635 | }, 636 | { 637 | "cell_type": "markdown", 638 | "metadata": { 639 | "id": "XxPj_QV43qD5" 640 | }, 641 | "source": [ 642 | "# Train Custom TF1 Object Detector\n", 643 | "\n", 644 | "We're ready to train our object detection model! Model training is performed using the \"model_main_tf2.py\" script from the TF Object Detection API. We've already defined all the parameters and arguments used by `model_main_tf2.py` in previous sections of this Colab. Training will take anywhere from 2 to 6 hours, depending on the model, batch size, and number of training steps. Just click Play on the following block to begin training!\n", 645 | "\n", 646 | "*Note: It takes a few minutes for the program to display any training messages, because it only displays logs once every 100 steps. If it seems like nothing is happening, just wait a couple minutes.*\n", 647 | "\n", 648 | "*Another note: If you want to stop training early, just click on the code block while it's running and press Ctrl+M to interrupt execution. You might have to press Ctrl+M a couple times and also click the Stop button or right-click and select \"Interrupt Execution\".*\n" 649 | ] 650 | }, 651 | { 652 | "cell_type": "code", 653 | "execution_count": null, 654 | "metadata": { 655 | "id": "3mSabQWqvC2R" 656 | }, 657 | "outputs": [], 658 | "source": [ 659 | "!pip install tensorboard==2.8.0" 660 | ] 661 | }, 662 | { 663 | "cell_type": "code", 664 | "execution_count": null, 665 | "metadata": { 666 | "id": "TI9iCCxoNlAL" 667 | }, 668 | "outputs": [], 669 | "source": [ 670 | "%load_ext tensorboard\n", 671 | "%tensorboard --logdir '/content/training'" 672 | ] 673 | }, 674 | { 675 | "cell_type": "code", 676 | "execution_count": null, 677 | "metadata": { 678 | "id": "tQTfZChVzzpZ" 679 | }, 680 | "outputs": [], 681 | "source": [ 682 | "!python /content/models/research/object_detection/model_main.py \\\n", 683 | " --pipeline_config_path={pipeline_file} \\\n", 684 | " --model_dir={model_dir} \\\n", 685 | " --alsologtostderr \\\n", 686 | " --num_train_steps={num_steps}" 687 | ] 688 | }, 689 | { 690 | "cell_type": "markdown", 691 | "metadata": { 692 | "id": "4Vk2146Ogil3" 693 | }, 694 | "source": [ 695 | "# 5. Export Trained Inference Graph\n", 696 | "In the left sidebar, click the folder icon to view the files in the `/content` folder. Click the `training` folder to expand it, and find the checkpoint with the highest number (e.g. `model.ckpt-29000`). Replace \"XXXX\" in the code section below with that number." 697 | ] 698 | }, 699 | { 700 | "cell_type": "code", 701 | "execution_count": null, 702 | "metadata": { 703 | "id": "YnSEZIzl4M10" 704 | }, 705 | "outputs": [], 706 | "source": [ 707 | "!mkdir /content/fine_tuned_model_lite\n", 708 | "output_directory = '/content/fine_tuned_model_lite'\n", 709 | "\n", 710 | "# Replace \"XXXX\" in the line below with the latest checkpoint in the /content/training directory\n", 711 | "last_model_path = '/content/training/model.ckpt-30000'\n", 712 | "print(last_model_path)\n", 713 | "\n", 714 | "!python /content/models/research/object_detection/export_tflite_ssd_graph.py \\\n", 715 | " --trained_checkpoint_prefix {last_model_path} \\\n", 716 | " --output_directory {output_directory} \\\n", 717 | " --pipeline_config_path {pipeline_file}" 718 | ] 719 | }, 720 | { 721 | "cell_type": "markdown", 722 | "metadata": { 723 | "id": "IwoGtqdejiez" 724 | }, 725 | "source": [ 726 | "# 6. Convert Model to TensorFlow Lite Format" 727 | ] 728 | }, 729 | { 730 | "cell_type": "code", 731 | "execution_count": null, 732 | "metadata": { 733 | "id": "PW7TpzLmhTbW" 734 | }, 735 | "outputs": [], 736 | "source": [ 737 | "localpb = '/content/fine_tuned_model_lite/tflite_graph.pb'\n", 738 | "tflite_file = '/content/fine_tuned_model_lite/detect.tflite'\n", 739 | "\n", 740 | "# Can use Netron app to determine the input size: https://www.electronjs.org/apps/netron \n", 741 | "input_size = [1, 300, 300, 3]\n", 742 | "input_shape = {\"normalized_input_image_tensor\" : input_size}\n", 743 | "\n", 744 | "print(input_shape)" 745 | ] 746 | }, 747 | { 748 | "cell_type": "code", 749 | "execution_count": null, 750 | "metadata": { 751 | "id": "Ec8LzJLbr-SR" 752 | }, 753 | "outputs": [], 754 | "source": [ 755 | "import tensorflow as tf\n", 756 | "\n", 757 | "converter = tf.lite.TFLiteConverter.from_frozen_graph(\n", 758 | " localpb, \n", 759 | " [\"normalized_input_image_tensor\"],\n", 760 | " [\"TFLite_Detection_PostProcess\",\"TFLite_Detection_PostProcess:1\",\"TFLite_Detection_PostProcess:2\",\"TFLite_Detection_PostProcess:3\"],\n", 761 | " input_shape\n", 762 | ")\n", 763 | "\n", 764 | "converter.allow_custom_ops = True\n", 765 | "converter.inference_type = tf.uint8 # Tell converter to create a quantized model\n", 766 | "converter.quantized_input_stats = {'normalized_input_image_tensor': (128, 128)}\n", 767 | "\n", 768 | "tflite_model = converter.convert()\n", 769 | "\n", 770 | "open(tflite_file,'wb').write(tflite_model)\n", 771 | "\n", 772 | "# Test that the conversion was successful - if any errors are generated, something went wrong!\n", 773 | "interpreter = tf.lite.Interpreter(model_content=tflite_model)\n", 774 | "interpreter.allocate_tensors()" 775 | ] 776 | }, 777 | { 778 | "cell_type": "code", 779 | "execution_count": null, 780 | "metadata": { 781 | "id": "awZMQGVqMpVL" 782 | }, 783 | "outputs": [], 784 | "source": [ 785 | "!cp /content/labelmap.txt /content/fine_tuned_model_lite\n", 786 | "!cp /content/labelmap.pbtxt /content/fine_tuned_model_lite\n", 787 | "!zip -r /content/fine_tuned_model_lite.zip /content/fine_tuned_model_lite" 788 | ] 789 | }, 790 | { 791 | "cell_type": "markdown", 792 | "metadata": { 793 | "id": "w41-KMn7KV-T" 794 | }, 795 | "source": [ 796 | "Now, you can download the \"detect.tflite\" and \"labelmap.txt\" files and move them to your Raspberry Pi." 797 | ] 798 | }, 799 | { 800 | "cell_type": "markdown", 801 | "metadata": { 802 | "id": "bQyjuMAt1WEL" 803 | }, 804 | "source": [ 805 | "# (Optional) Test out TensorFlow Lite Model\n", 806 | "Now that we've got our custom detection model trained and converted to TFLite format, let's try it out on some images. We'll use the images from our \"test\" folder. These images weren't seen at all by the model during training, so they'll give a faithful depiction of how accurate the model actually is.\n", 807 | "\n", 808 | "The following code is used to import the TensorFlow Lite runtime, load it into memory, run inferencing on the test images, and display the results." 809 | ] 810 | }, 811 | { 812 | "cell_type": "code", 813 | "execution_count": null, 814 | "metadata": { 815 | "id": "v1IBMfrD2GEQ" 816 | }, 817 | "outputs": [], 818 | "source": [ 819 | "# Copied from my TFLite repository, and stripped unneeded stuff out\n", 820 | "\n", 821 | "# Import packages\n", 822 | "import os\n", 823 | "import cv2\n", 824 | "import numpy as np\n", 825 | "import sys\n", 826 | "import glob\n", 827 | "import importlib.util\n", 828 | "from tensorflow.lite.python.interpreter import Interpreter\n", 829 | "\n", 830 | "import matplotlib\n", 831 | "import matplotlib.pyplot as plt\n", 832 | "\n", 833 | "%matplotlib inline\n", 834 | "\n", 835 | "PATH_TO_IMAGES='/content/images/test'\n", 836 | "PATH_TO_MODEL='/content/fine_tuned_model_lite/detect.tflite'\n", 837 | "PATH_TO_LABELS='/content/labelmap.txt'\n", 838 | "IMAGES_TO_TEST = 10\n", 839 | "min_conf_threshold=0.5\n", 840 | "\n", 841 | "# Grab all image filenames\n", 842 | "images = glob.glob(PATH_TO_IMAGES + '/*.jpg')\n", 843 | "images = images + glob.glob(PATH_TO_IMAGES + '/*.JPG')\n", 844 | "images = images + glob.glob(PATH_TO_IMAGES + '/*.png')\n", 845 | "\n", 846 | "\n", 847 | "# Load the label map into memory\n", 848 | "with open(PATH_TO_LABELS, 'r') as f:\n", 849 | " labels = [line.strip() for line in f.readlines()]\n", 850 | "\n", 851 | "\n", 852 | "# Load the Tensorflow Lite model into memory\n", 853 | "interpreter = Interpreter(model_path=PATH_TO_MODEL)\n", 854 | "interpreter.allocate_tensors()\n", 855 | "\n", 856 | "# Get model details\n", 857 | "input_details = interpreter.get_input_details()\n", 858 | "output_details = interpreter.get_output_details()\n", 859 | "height = input_details[0]['shape'][1]\n", 860 | "width = input_details[0]['shape'][2]\n", 861 | "\n", 862 | "floating_model = (input_details[0]['dtype'] == np.float32)\n", 863 | "\n", 864 | "input_mean = 127.5\n", 865 | "input_std = 127.5\n", 866 | "\n", 867 | "# Loop over every image and perform detection\n", 868 | "for image_path in images[0:IMAGES_TO_TEST]:\n", 869 | "\n", 870 | " # Load image and resize to expected shape [1xHxWx3]\n", 871 | " image = cv2.imread(image_path)\n", 872 | " image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)\n", 873 | " imH, imW, _ = image.shape \n", 874 | " image_resized = cv2.resize(image_rgb, (width, height))\n", 875 | " input_data = np.expand_dims(image_resized, axis=0)\n", 876 | "\n", 877 | " # Normalize pixel values if using a floating model (i.e. if model is non-quantized)\n", 878 | " if floating_model:\n", 879 | " input_data = (np.float32(input_data) - input_mean) / input_std\n", 880 | "\n", 881 | " # Perform the actual detection by running the model with the image as input\n", 882 | " interpreter.set_tensor(input_details[0]['index'],input_data)\n", 883 | " interpreter.invoke()\n", 884 | "\n", 885 | " # Retrieve detection results\n", 886 | " boxes = interpreter.get_tensor(output_details[0]['index'])[0] # Bounding box coordinates of detected objects\n", 887 | " classes = interpreter.get_tensor(output_details[1]['index'])[0] # Class index of detected objects\n", 888 | " scores = interpreter.get_tensor(output_details[2]['index'])[0] # Confidence of detected objects\n", 889 | " #num = interpreter.get_tensor(output_details[3]['index']) # Total number of detected objects (inaccurate and not needed)\n", 890 | "\n", 891 | " # Loop over all detections and draw detection box if confidence is above minimum threshold\n", 892 | " for i in range(len(scores)):\n", 893 | " if ((scores[i] > min_conf_threshold) and (scores[i] <= 1.0)):\n", 894 | "\n", 895 | " # Get bounding box coordinates and draw box\n", 896 | " # Interpreter can return coordinates that are outside of image dimensions, need to force them to be within image using max() and min()\n", 897 | " ymin = int(max(1,(boxes[i][0] * imH)))\n", 898 | " xmin = int(max(1,(boxes[i][1] * imW)))\n", 899 | " ymax = int(min(imH,(boxes[i][2] * imH)))\n", 900 | " xmax = int(min(imW,(boxes[i][3] * imW)))\n", 901 | " \n", 902 | " cv2.rectangle(image, (xmin,ymin), (xmax,ymax), (10, 255, 0), 2)\n", 903 | "\n", 904 | " # Draw label\n", 905 | " object_name = labels[int(classes[i])] # Look up object name from \"labels\" array using class index\n", 906 | " label = '%s: %d%%' % (object_name, int(scores[i]*100)) # Example: 'person: 72%'\n", 907 | " labelSize, baseLine = cv2.getTextSize(label, cv2.FONT_HERSHEY_SIMPLEX, 0.7, 2) # Get font size\n", 908 | " label_ymin = max(ymin, labelSize[1] + 10) # Make sure not to draw label too close to top of window\n", 909 | " cv2.rectangle(image, (xmin, label_ymin-labelSize[1]-10), (xmin+labelSize[0], label_ymin+baseLine-10), (255, 255, 255), cv2.FILLED) # Draw white box to put label text in\n", 910 | " cv2.putText(image, label, (xmin, label_ymin-7), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 0), 2) # Draw label text\n", 911 | "\n", 912 | " # All the results have been drawn on the image, now display the image\n", 913 | " image = cv2.cvtColor(image,cv2.COLOR_BGR2RGB)\n", 914 | " plt.figure(figsize=(12,16))\n", 915 | " plt.imshow(image)\n", 916 | " plt.show()" 917 | ] 918 | }, 919 | { 920 | "cell_type": "markdown", 921 | "metadata": { 922 | "id": "XFsuasvxFHo8" 923 | }, 924 | "source": [ 925 | "## (Optional) Compile Model for Edge TPU\n", 926 | "\n", 927 | "Now that the model has been converted to TFLite and quantized, we can compile it to run on Edge TPU devices like the [Coral USB Accelerator](https://coral.ai/products/accelerator/) or the [Coral Dev Board](https://coral.ai/products/dev-board/). This allows the model to run much faster! For more information on the Edge TPU, see my [TensorFlow Lite repository on GitHub](fine_tuned_model_lite).\n", 928 | "\n", 929 | "First, install the Edge TPU Compiler package inside this Colab instance." 930 | ] 931 | }, 932 | { 933 | "cell_type": "code", 934 | "execution_count": null, 935 | "metadata": { 936 | "id": "mUd_SNC0JSq0" 937 | }, 938 | "outputs": [], 939 | "source": [ 940 | "! curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key add -\n", 941 | "! echo \"deb https://packages.cloud.google.com/apt coral-edgetpu-stable main\" | sudo tee /etc/apt/sources.list.d/coral-edgetpu.list\n", 942 | "! sudo apt-get update\n", 943 | "! sudo apt-get install edgetpu-compiler\t" 944 | ] 945 | }, 946 | { 947 | "cell_type": "markdown", 948 | "metadata": { 949 | "id": "usfmdtSiJuuC" 950 | }, 951 | "source": [ 952 | "Next, compile the model. (If your model has a different filename than \"detect.tflite\", use that instead.)" 953 | ] 954 | }, 955 | { 956 | "cell_type": "code", 957 | "execution_count": null, 958 | "metadata": { 959 | "id": "WTboEAWuJ0ku" 960 | }, 961 | "outputs": [], 962 | "source": [ 963 | "%cd /content/fine_tuned_model_lite\n", 964 | "!edgetpu_compiler detect.tflite\n", 965 | "!mv detect_edgetpu.tflite edgetpu.tflite" 966 | ] 967 | }, 968 | { 969 | "cell_type": "markdown", 970 | "metadata": { 971 | "id": "oqGy2FgzKomN" 972 | }, 973 | "source": [ 974 | "The compiled model will be output in the /content folder as \"detect_edgetpu.tflite\". It gets renamed to \"edgetpu.tflite\" to be consistent with my code. Download this file using the command below. (If your model has a different filename than \"edgetpu.tflite\", use that instead.)" 975 | ] 976 | }, 977 | { 978 | "cell_type": "code", 979 | "execution_count": null, 980 | "metadata": { 981 | "id": "GgvZC_y5ESYq" 982 | }, 983 | "outputs": [], 984 | "source": [ 985 | "%cd /content\n", 986 | "!cp labelmap.txt fine_tuned_model_lite\n", 987 | "!cp labelmap.pbtxt fine_tuned_model_lite\n", 988 | "!zip -r fine_tuned_model_lite.zip fine_tuned_model_lite" 989 | ] 990 | }, 991 | { 992 | "cell_type": "code", 993 | "execution_count": null, 994 | "metadata": { 995 | "id": "AmjqvKuuK8ZR" 996 | }, 997 | "outputs": [], 998 | "source": [ 999 | "from google.colab import files\n", 1000 | "\n", 1001 | "files.download('edgetpu.tflite')" 1002 | ] 1003 | }, 1004 | { 1005 | "cell_type": "markdown", 1006 | "metadata": { 1007 | "id": "ptwpBBEWLfuJ" 1008 | }, 1009 | "source": [ 1010 | "Now you're all set to use the Coral model! For instructions on how to run an object detection model on the Raspberry Pi using the Coral USB Acclerator, please see my video, [\"How to Use the Coral USB Accelerator with the Raspberry Pi\"](https://www.youtube.com/watch?v=qJMwNHQNOVU)." 1011 | ] 1012 | } 1013 | ], 1014 | "metadata": { 1015 | "accelerator": "GPU", 1016 | "colab": { 1017 | "collapsed_sections": [], 1018 | "provenance": [], 1019 | "include_colab_link": true 1020 | }, 1021 | "kernelspec": { 1022 | "display_name": "Python 3", 1023 | "name": "python3" 1024 | } 1025 | }, 1026 | "nbformat": 4, 1027 | "nbformat_minor": 0 1028 | } -------------------------------------------------------------------------------- /deploy_guides/MacOS_TFLite_Guide.md: -------------------------------------------------------------------------------- 1 | # How to Run TensorFlow Lite Models on macOS 2 | This guide shows how to set up a TensorFlow Lite Runtime environment on a macOS device. We'll use [Anaconda](https://www.anaconda.com/) to create a Python environment to install the TFLite Runtime in. It's easy! 3 | 4 | Acknowledgement: Thanks goes to [Max Hancock](https://www.linkedin.com/in/maxwell-hancock/) for contributing this guide! 5 | 6 | ## Step 1. Download and Install Anaconda 7 | First, install [Anaconda](https://www.anaconda.com/), which is a Python environment manager that greatly simplifies Python package management and deployment. Anaconda allows you to create Python virtual environments on your Mac without interfering with existing installations of Python. Go to the [Anaconda Downloads page](https://www.anaconda.com/products/distribution) and click the Download button. 8 | 9 | When the download finishes, open the downloaded .exe file and step through the installation wizard. Use the default install options. 10 | 11 | ## Step 2. Set Up Virtual Environment and Directory 12 | First open up the terminal by opening a Finder window, and press 'Command + Shift + U', and then select Terminal. We'll create a folder called `tflite1` directly in the Home folder (under your username) - you can use any other folder location you like, just make sure to modify the commands below to use the correct file paths. Create the folder and move into it by issuing the following commands in the terminal: 13 | 14 | ``` 15 | mkdir ~/tflite1 16 | cd ~/tflite1 17 | ``` 18 | 19 | Next, create a Python 3.9 virtual environment by issuing: 20 | 21 | ``` 22 | conda create --name tflite1-env python=3.9 23 | ``` 24 | 25 | Enter "y" then "ENTER" when it asks if you want to proceed. Activate the environment and install the required packages by issuing the commands below. We'll install TensorFlow, OpenCV, and a downgraded version of protobuf. TensorFlow is a pretty big download (about 450MB), so it will take a while. 26 | 27 | ``` 28 | conda activate tflite1-env 29 | pip install tensorflow 30 | pip install opencv-python 31 | pip uninstall protobuf 32 | pip install protobuf==3.20.0 33 | ``` 34 | 35 | Download the detection scripts from this repository by issuing: 36 | 37 | ``` 38 | curl https://raw.githubusercontent.com/EdjeElectronics/TensorFlow-Lite-Object-Detection-on-Android-and-Raspberry-Pi/master/TFLite_detection_image.py --output TFLite_detection_image.py 39 | curl https://raw.githubusercontent.com/EdjeElectronics/TensorFlow-Lite-Object-Detection-on-Android-and-Raspberry-Pi/master/TFLite_detection_video.py --output TFLite_detection_video.py 40 | curl https://raw.githubusercontent.com/EdjeElectronics/TensorFlow-Lite-Object-Detection-on-Android-and-Raspberry-Pi/master/TFLite_detection_webcam.py --output TFLite_detection_webcam.py 41 | curl https://raw.githubusercontent.com/EdjeElectronics/TensorFlow-Lite-Object-Detection-on-Android-and-Raspberry-Pi/master/TFLite_detection_stream.py --output TFLite_detection_stream.py 42 | ``` 43 | 44 | ## Step 3. Move TFLite Model into Directory 45 | Next, take the custom TFLite model that was trained and downloaded from the Colab notebook and move it into the {username}\tflite1 directory (replacing {username} with your home directory name). If you downloaded it from Colab, it should be in a file called `custom_model_lite.zip`. (If you haven't trained a model yet and just want to test one out, download my "change counter" model by clicking this [Dropbox link](https://www.dropbox.com/scl/fi/4fk8ls8s03c94g6sb3ngo/custom_model_lite.zip?rlkey=zqda21sowk0hrw6i3f2dgbsyy&dl=0).) Move that file to the {username}\tflite1 directory. Once it's moved, unzip it using: 46 | 47 | ``` 48 | tar -xf custom_model_lite.zip 49 | ``` 50 | 51 | At this point, you should have a folder at {username}\tflite1\custom_model_lite which contains at least a `detect.tflite` and `labelmap.txt` file. 52 | 53 | ## Step 4. Run TensorFlow Lite Model! 54 | Now, just call one of the detection scripts and point it at your model folder with the `--modeldir` option. For example, to run your `custom_model_lite` model on a webcam, issue: 55 | 56 | ``` 57 | python TFLite_detection_webcam.py --modeldir=custom_model_lite 58 | ``` 59 | 60 | A window will appear showing detection results drawn on the live webcam feed, make sure to accept the use of webcam. For more information on how to use the detection scripts, like if you want to enter an image, video, or web stream please see [Step 3 in the main README page](https://github.com/EdjeElectronics/TensorFlow-Lite-Object-Detection-on-Android-and-Raspberry-Pi#step-3-run-tensorflow-lite-models). 61 | 62 | Have fun using TensorFlow Lite! Stay tuned for more examples on how to build cool applications around your model. 63 | -------------------------------------------------------------------------------- /deploy_guides/Raspberry_Pi_Guide.md: -------------------------------------------------------------------------------- 1 | # How to Run TensorFlow Lite Object Detection Models on the Raspberry Pi (with Optional Coral USB Accelerator) 2 | 3 |

4 | 5 |

6 | 7 | ## Introduction 8 | This guide provides step-by-step instructions for how to set up TensorFlow Lite on the Raspberry Pi and use it to run object detection models. It also shows how to set up the Coral USB Accelerator on the Pi and run Edge TPU detection models. It works for the Raspberry Pi 3 and Raspberry Pi 4 running either Rasbpian Buster or Rasbpian Stretch. 9 | 10 | This guide is part of my larger TensorFlow Lite tutorial series which shows how to [train, convert, and run custom TensorFlow Lite object detection models](https://github.com/EdjeElectronics/TensorFlow-Lite-Object-Detection-on-Android-and-Raspberry-Pi). 11 | 12 | TensorFlow Lite (TFLite) models run much faster than regular TensorFlow models on the Raspberry Pi. You can see a comparison of framerates obtained using regular TensorFlow, TensorFlow Lite, and Coral USB Accelerator models in my [TensorFlow Lite Performance Comparison YouTube video](https://www.youtube.com/watch?v=TiOKvOrYNII). 13 | 14 | This portion of the guide is split in to three sections: 15 | 16 | * [Section 1. Run TensorFlow Lite Object Detection Models on the Raspberry Pi](#section-1---how-to-set-up-and-run-tensorflow-lite-object-detection-models-on-the-raspberry-pi) 17 | * [Section 2. Run Edge TPU Object Detection Models on the Raspberry Pi Using the Coral USB Accelerator](#section-2---run-edge-tpu-object-detection-models-on-the-raspberry-pi-using-the-coral-usb-accelerator) 18 | * [Section 3. Compile Custom Edge TPU Object Detection Models](#section-2---run-edge-tpu-object-detection-models-on-the-raspberry-pi-using-the-coral-usb-accelerator) 19 | 20 | This repository also includes scripts for running the TFLite and Edge TPU models on images, videos, or webcam/Picamera feeds. 21 | 22 | ## Section 1 - How to Set Up and Run TensorFlow Lite Object Detection Models on the Raspberry Pi 23 | 24 | Setting up TensorFlow Lite on the Raspberry Pi is much easier than regular TensorFlow! These are the steps needed to set up TensorFlow Lite: 25 | 26 | - 1a. Update the Raspberry Pi 27 | - 1b. Download this repository and create virtual environment 28 | - 1c. Install TensorFlow and OpenCV 29 | - 1d. Set up TensorFlow Lite detection model 30 | - 1e. Run TensorFlow Lite model! 31 | 32 | I also made a YouTube video that walks through this guide: 33 | 34 | [![Link to my YouTube video!](https://raw.githubusercontent.com/EdjeElectronics/TensorFlow-Lite-Object-Detection-on-Android-and-Raspberry-Pi/master/doc/YouTube_video1.JPG)](https://www.youtube.com/watch?v=aimSGOAUI8Y) 35 | 36 | ### Step 1a. Update the Raspberry Pi 37 | First, the Raspberry Pi needs to be fully updated. Open a terminal and issue: 38 | ``` 39 | sudo apt-get update 40 | sudo apt-get dist-upgrade 41 | ``` 42 | Depending on how long it’s been since you’ve updated your Pi, the update could take anywhere between a minute and an hour. 43 | 44 | While we're at it, let's make sure the camera interface is enabled in the Raspberry Pi Configuration menu. Click the Pi icon in the top left corner of the screen, select Preferences -> Raspberry Pi Configuration, and go to the Interfaces tab and verify Camera is set to Enabled. If it isn't, enable it now, and reboot the Raspberry Pi. 45 | 46 |

47 | 48 |

49 | 50 | ### Step 1b. Download this repository and create virtual environment 51 | 52 | Next, clone this GitHub repository by issuing the following command. The repository contains the scripts we'll use to run TensorFlow Lite, as well as a shell script that will make installing everything easier. Issue: 53 | 54 | ``` 55 | git clone https://github.com/EdjeElectronics/TensorFlow-Lite-Object-Detection-on-Android-and-Raspberry-Pi.git 56 | ``` 57 | 58 | This downloads everything into a folder called TensorFlow-Lite-Object-Detection-on-Android-and-Raspberry-Pi. That's a little long to work with, so rename the folder to "tflite1" and then cd into it: 59 | 60 | ``` 61 | mv TensorFlow-Lite-Object-Detection-on-Android-and-Raspberry-Pi tflite1 62 | cd tflite1 63 | ``` 64 | 65 | We'll work in this /home/pi/tflite1 directory for the rest of the guide. Next up is to create a virtual environment called "tflite1-env". 66 | 67 | I'm using a virtual environment for this guide because it prevents any conflicts between versions of package libraries that may already be installed on your Pi. Keeping TensorFlow installed in its own environment allows us to avoid version conflicts. For example, if you've already installed TensorFlow v1.8 on the Pi using my [other guide](https://www.youtube.com/watch?v=npZ-8Nj1YwY), you can leave that installation as-is without having to worry about overriding it. 68 | 69 | Install virtualenv by issuing: 70 | 71 | ``` 72 | sudo pip3 install virtualenv 73 | ``` 74 | 75 | Then, create the "tflite1-env" virtual environment by issuing: 76 | 77 | ``` 78 | python3 -m venv tflite1-env 79 | ``` 80 | 81 | This will create a folder called tflite1-env inside the tflite1 directory. The tflite1-env folder will hold all the package libraries for this environment. Next, activate the environment by issuing: 82 | 83 | ``` 84 | source tflite1-env/bin/activate 85 | ``` 86 | 87 | **You'll need to issue the `source tflite1-env/bin/activate` command from inside the /home/pi/tflite1 directory to reactivate the environment every time you open a new terminal window. You can tell when the environment is active by checking if (tflite1-env) appears before the path in your command prompt, as shown in the screenshot below.** 88 | 89 | At this point, here's what your tflite1 directory should look like if you issue `ls`. 90 | 91 |

92 | 93 |

94 | 95 | If your directory looks good, it's time to move on to Step 1c! 96 | 97 | ### Step 1c. Install TensorFlow Lite dependencies and OpenCV 98 | Next, we'll install TensorFlow, OpenCV, and all the dependencies needed for both packages. OpenCV is not needed to run TensorFlow Lite, but the object detection scripts in this repository use it to grab images and draw detection results on them. 99 | 100 | To make things easier, I wrote a shell script that will automatically download and install all the packages and dependencies. Run it by issuing: 101 | 102 | ``` 103 | bash get_pi_requirements.sh 104 | ``` 105 | 106 | This downloads about 400MB worth of installation files, so it will take a while. Go grab a cup of coffee while it's working! If you'd like to see everything that gets installed, simply open get_pi_dependencies.sh to view the list of packages. 107 | 108 | **NOTE: If you get an error while running the `bash get_pi_requirements.sh` command, it's likely because your internet connection timed out, or because the downloaded package data was corrupted. If you get an error, try re-running the command a few more times.** 109 | 110 | **ANOTHER NOTE: The shell script automatically installs the latest version of TensorFlow. If you'd like to install a specific version, issue `pip3 install tensorflow==X.XX` (where X.XX is replaced with the version you want to install) after running the script. This will override the existing installation with the specified version.** 111 | 112 | That was easy! On to the next step. 113 | 114 | ### Step 1d. Set up TensorFlow Lite detection model 115 | Next, we'll set up the detection model that will be used with TensorFlow Lite. This guide shows how to either download a sample TFLite model provided by Google, or how to use a model that you've trained yourself by following [my TensorFlow Lite training guide](https://github.com/EdjeElectronics/TensorFlow-Lite-Object-Detection-on-Android-and-Raspberry-Pi). 116 | 117 | A detection model has two files associated with it: a detect.tflite file (which is the model itself) and a labelmap.txt file (which provides a labelmap for the model). My preferred way to organize the model files is to create a folder (such as "BirdSquirrelRaccoon_TFLite_model") and keep both the detect.tflite and labelmap.txt in that folder. This is also how Google's downloadable sample TFLite model is organized. 118 | 119 | #### Option 1. Using Google's sample TFLite model 120 | Google provides a sample quantized SSDLite-MobileNet-v2 object detection model which is trained off the MSCOCO dataset and converted to run on TensorFlow Lite. It can detect and identify 80 different common objects, such as people, cars, cups, etc. 121 | 122 | Download the sample model (which can be found on [the Object Detection page of the official TensorFlow website](https://www.tensorflow.org/lite/models/object_detection/overview)) by issuing: 123 | 124 | ``` 125 | wget https://storage.googleapis.com/download.tensorflow.org/models/tflite/coco_ssd_mobilenet_v1_1.0_quant_2018_06_29.zip 126 | ``` 127 | 128 | Unzip it to a folder called "Sample_TFLite_model" by issuing (this command automatically creates the folder): 129 | 130 | ``` 131 | unzip coco_ssd_mobilenet_v1_1.0_quant_2018_06_29.zip -d Sample_TFLite_model 132 | ``` 133 | 134 | Okay, the sample model is all ready to go! 135 | 136 | #### Option 2: Using your own custom-trained model 137 | You can also use a custom object detection model by moving the model folder into the /home/pi/tflite directory. If you followed [my TensorFlow Lite training guide](https://github.com/EdjeElectronics/TensorFlow-Lite-Object-Detection-on-Android-and-Raspberry-Pi) to train and convert a TFLite model on your PC, you should have a folder named "TFLite_model" with a detect.tflite and labelmap.txt file. (It will also have a tflite_graph.pb and tflite_graph.pbtxt file, which are not needed by TensorFlow Lite but can be left in the folder.) 138 | 139 | You can simply copy that folder to a USB drive, insert the USB drive in your Raspberry Pi, and move the folder into the /home/pi/tflite1 directory. (Or you can email it to yourself, or put it on Google Drive, or do whatever your preferred method of file transfer is.) Here's an example of what my "BirdSquirrelRaccoon_TFLite_model" folder looks like in my /home/pi/tflite1 directory: 140 | 141 |

142 | 143 |

144 | 145 | Now your custom model is ready to go! 146 | 147 | ### Step 1e. Run the TensorFlow Lite model! 148 | It's time to see the TFLite object detection model in action! First, free up memory and processing power by closing any applications you aren't using. Also, make sure you have your webcam or Picamera plugged in. 149 | 150 | Run the real-time webcam detection script by issuing the following command from inside the /home/pi/tflite1 directory. (Before running the command, make sure the tflite1-env environment is active by checking that (tflite1-env) appears in front of the command prompt.) **The TFLite_detection_webcam.py script will work with either a Picamera or a USB webcam.** 151 | 152 | ``` 153 | python3 TFLite_detection_webcam.py --modeldir=Sample_TFLite_model 154 | ``` 155 | 156 | If your model folder has a different name than "Sample_TFLite_model", use that name instead. For example, I would use `--modeldir=BirdSquirrelRaccoon_TFLite_model` to run my custom bird, squirrel, and raccoon detection model. 157 | 158 | After a few moments of initializing, a window will appear showing the webcam feed. Detected objects will have bounding boxes and labels displayed on them in real time. 159 | 160 | The main page of my TensorFlow Lite training guide gives [instructions](https://github.com/EdjeElectronics/TensorFlow-Lite-Object-Detection-on-Android-and-Raspberry-Pi#video) for using the TFLite_detection_image.py and TFLite_detection_video.py scripts. Make sure to use `python3` rather than `python` when running the scripts. 161 | 162 | ## Section 2 - Run Edge TPU Object Detection Models on the Raspberry Pi Using the Coral USB Accelerator 163 | 164 | [![Link to Section 2 YouTube video!](https://raw.githubusercontent.com/EdjeElectronics/TensorFlow-Lite-Object-Detection-on-Android-and-Raspberry-Pi/master/doc/YouTube_video2.png)](https://www.youtube.com/watch?v=qJMwNHQNOVU) 165 | 166 | The [Coral USB Accelerator](https://coral.withgoogle.com/products/accelerator/) is a USB hardware accessory for speeding up TensorFlow models. You can buy one [here (Amazon Associate link)](https://amzn.to/2BuG1Tv). 167 | 168 | The USB Accelerator uses the Edge TPU (tensor processing unit), which is an ASIC (application-specific integrated circuit) chip specially designed with highly parallelized ALUs (arithmetic logic units). While GPUs (graphics processing units) also have many parallelized ALUs, the TPU has one key difference: the ALUs are directly connected to eachother. The output of one ALU can be directly passed to the input of the next ALU without having to be stored and retrieved from a memory buffer. The extreme paralellization and removal of the memory bottleneck means the TPU can perform up to 4 trillion arithmetic operations per second! This is perfect for running deep neural networks, which require millions of multiply-accumulate operations to generate outputs from a single batch of input data. 169 | 170 |

171 | 172 |

173 | 174 | My Master's degree was in ASIC design, so the Edge TPU is very interesting to me! If you're a computer architecture nerd like me and want to learn more about the Edge TPU, [here is a great article that explains how it works](https://cloud.google.com/blog/products/ai-machine-learning/what-makes-tpus-fine-tuned-for-deep-learning). 175 | 176 | It makes object detection models run WAY faster, and it's easy to set up. These are the steps we'll go through to set up the Coral USB Accelerator: 177 | 178 | - 2a. Install libedgetpu library 179 | - 2b. Set up Edge TPU detection model 180 | - 2c. Run super-speed detection! 181 | 182 | This section of the guide assumes you have already completed [Section 1](https://github.com/EdjeElectronics/TensorFlow-Lite-Object-Detection-on-Android-and-Raspberry-Pi/blob/master/Raspberry_Pi_Guide.md#section-1---how-to-set-up-and-run-tensorflow-lite-object-detection-models-on-the-raspberry-pi) for setting up TFLite object detection on the Pi. If you haven't done that portion, scroll back up and work through it first. 183 | 184 | ### Step 2a. Install libedgetpu library 185 | First, we'll download and install the Edge TPU runtime, which is the library needed to interface with the USB Acccelerator. These instructions follow the [USB Accelerator setup guide](https://coral.withgoogle.com/docs/accelerator/get-started/) from official Coral website. 186 | 187 | Open a command terminal and move into the /home/pi/tflite1 directory and activate the tflite1-env virtual environment by issuing: 188 | 189 | ``` 190 | cd /home/pi/tflite1 191 | source tflite1-env/bin/activate 192 | ``` 193 | 194 | Add the Coral package repository to your apt-get distribution list by issuing the following commands: 195 | 196 | ``` 197 | echo "deb https://packages.cloud.google.com/apt coral-edgetpu-stable main" | sudo tee /etc/apt/sources.list.d/coral-edgetpu.list 198 | curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key add - 199 | sudo apt-get update 200 | ``` 201 | 202 | Install the libedgetpu library by issuing: 203 | 204 | ``` 205 | sudo apt-get install libedgetpu1-std 206 | ``` 207 | 208 | You can also install the libedgetpu1-max library, which runs the USB Accelerator at an overclocked frequency, allowing it to achieve even faster framerates. However, it also causes the USB Accelerator to get hotter. Here are the framerates I get when running TFLite_detection_webcam.py with 1280x720 resolution for each option with a Raspberry Pi 4 4GB model: 209 | 210 | * libedgetpu1-std: 22.6 FPS 211 | * libedgetpu1-max: 26.1 FPS 212 | 213 | I didn't measure the temperature of the USB Accelerator, but it does get a little hotter to the touch with the libedgetpu1-max version. However, it didn't seem hot enough to be unsafe or harmful to the electronics. 214 | 215 | If you want to use the libedgetpu-max library, install it by using `sudo apt-get install libedgetpu1-max`. (You can't have both the -std and the -max libraries installed. If you install the -max library, the -std library will automatically be uninstalled.) 216 | 217 | Alright! Now that the libedgetpu runtime is installed, it's time to set up an Edge TPU detection model to use it with. 218 | 219 | ### Step 2b. Set up Edge TPU detection model 220 | Edge TPU models are TensorFlow Lite models that have been compiled specifically to run on Edge TPU devices like the Coral USB Accelerator. They reside in a .tflite file and are used the same way as a regular TF Lite model. My preferred method is to keep the Edge TPU file in the same model folder as the TFLite model it was compiled from, and name it as "edgetpu.tflite". 221 | 222 | I'll show two options for setting up an Edge TPU model: using the sample model from Google, or using a custom model you compiled yourself. 223 | 224 | #### Option 1. Using Google's sample EdgeTPU model 225 | Google provides a sample Edge TPU model that is compiled from the quantized SSDLite-MobileNet-v2 we used in [Step 1e](https://github.com/EdjeElectronics/TensorFlow-Lite-Object-Detection-on-Android-and-Raspberry-Pi/blob/master/Raspberry_Pi_Guide.md#step-1e-set-up-tensorflow-lite-detection-model). Download it and move it into the Sample_TFLite_model folder (while simultaneously renaming it to "edgetpu.tflite") by issuing these commands: 226 | 227 | ``` 228 | wget https://dl.google.com/coral/canned_models/mobilenet_ssd_v2_coco_quant_postprocess_edgetpu.tflite 229 | 230 | mv mobilenet_ssd_v2_coco_quant_postprocess_edgetpu.tflite Sample_TFLite_model/edgetpu.tflite 231 | ``` 232 | 233 | Now the sample Edge TPU model is all ready to go. It will use the same labelmap.txt file as the TFLite model, which should already be located in the Sample_TFLite_model folder. 234 | 235 | #### Option 2. Using your own custom EdgeTPU model 236 | If you trained a custom TFLite detection model, you can compile it for use with the Edge TPU. Unfortunately, the edgetpu-compiler package doesn't work on the Raspberry Pi: you need a Linux PC to use it on. Section 3 of this guide gives a couple options for compiling your own model if you don't have a Linux PC. [Here are the official instructions that show how to compile an Edge TPU model from a TFLite model](https://coral.withgoogle.com/docs/edgetpu/compiler/). 237 | 238 | Assuming you've been able to compile your TFLite model into an EdgeTPU model, you can simply copy the .tflite file onto a USB and transfer it to the model folder on your Raspberry Pi. For my "BirdSquirrelRaccoon_TFLite_model" example from [Step 1e](https://github.com/EdjeElectronics/TensorFlow-Lite-Object-Detection-on-Android-and-Raspberry-Pi/blob/master/Raspberry_Pi_Guide.md#step-1e-set-up-tensorflow-lite-detection-model), I can compile my "BirdSquirrelRaccoon_TFLite_model" on a Linux PC, put the resulting edgetpu.tflite file on a USB, transfer the USB to my Pi, and move the edgetpu.tflite file into the /home/pi/tflite1/BirdSquirrelRaccoon_TFLite_model folder. It will use the same labelmap.txt file that already exists in the folder to get its labels. 239 | 240 | Once the edgetpu.tflite file has been moved into the model folder, it's ready to go! 241 | 242 | ### Step 2c. Run detection with Edge TPU! 243 | 244 | Now that everything is set up, it's time to test out the Coral's ultra-fast detection speed! Make sure to free up memory and processing power by closing any programs you aren't using. Make sure you have a webcam plugged in. 245 | 246 | Plug in your Coral USB Accelerator into one of the USB ports on the Raspberry Pi. If you're using a Pi 4, make sure to plug it in to one of the blue USB 3.0 ports. 247 | 248 | Make sure the tflite1-env environment is active by checking that (tflite1-env) appears in front of the command prompt in your terminal. Then, run the real-time webcam detection script with the --edgetpu argument: 249 | 250 | ``` 251 | python3 TFLite_detection_webcam.py --modeldir=Sample_TFLite_model --edgetpu 252 | ``` 253 | 254 | The `--edgetpu` argument tells the script to use the Coral USB Accelerator and the EdgeTPU-compiled .tflite file. If your model folder has a different name than "Sample_TFLite_model", use that name instead. 255 | 256 | After a brief initialization period, a window will appear showing the webcam feed with detections drawn on each from. The detection will run SIGNIFICANTLY faster with the Coral USB Accelerator. 257 | 258 | If you'd like to run the video or image detection scripts with the Accelerator, use these commands: 259 | 260 | ``` 261 | python3 TFLite_detection_video.py --modeldir=Sample_TFLite_model --edgetpu 262 | python3 TFLite_detection_image.py --modeldir=Sample_TFLite_model --edgetpu 263 | ``` 264 | 265 | Have fun with the blazing detection speeds of the Coral USB Accelerator! 266 | 267 | ## Section 3 - Compile Custom Edge TPU Object Detection Models 268 | 269 | To use a custom model on the Coral USB Accelerator, you have to run it through Coral's [Edge TPU Compiler](https://coral.ai/docs/edgetpu/compiler/) tool. Unfortunately, the compiler only works on Linux operating systems, and only on certain CPU architectures. 270 | 271 | The easiest way to compile the Edge TPU model is to use a Google Colab session. I created a Colab page specifically for compiling Edge TPU models. Please click the link below and follow the instructions in the Colab notebook. 272 | 273 | https://colab.research.google.com/drive/1o6cNNNgGhoT7_DR4jhpMKpq3mZZ6Of4N?usp=sharing 274 | 275 | ## Appendix: Common Errors 276 | This appendix lists common errors that have been encountered by users following this guide, and solutions showing how to resolve them. 277 | 278 | **Feel free to create Pull Requests to add your own errors and resolutions! I'd appreciate any help.** 279 | 280 | ### 1. TypeError: int() argument must be a string, a bytes-like object or a number, not 'NoneType' 281 | The 'NoneType' error means that the program received an empty array from the webcam, which typically means something is wrong with the webcam or the interface to the webcam. Try plugging and re-plugging the webcam in a few times, and/or power cycling the Raspberry Pi, and see if that works. If not, you may need to try using a new webcam. 282 | 283 | ### 2. ImportError: No module named 'cv2' 284 | This error occurs when you try to run any of the TFLite_detection scripts without activating the 'tflite1-env' first. It happens because Python cannot find the path to the OpenCV library (cv2) to import it. 285 | 286 | Resolve the issue by closing your terminal window, re-opening it, and issuing: 287 | 288 | ``` 289 | cd tflite1 290 | source tflite1-env/bin/activate 291 | ``` 292 | 293 | Then, try re-running the script as described in [Step 1e](https://github.com/EdjeElectronics/TensorFlow-Lite-Object-Detection-on-Android-and-Raspberry-Pi/blob/master/Raspberry_Pi_Guide.md#step-1e-run-the-tensorflow-lite-model). 294 | 295 | ### 3. THESE PACKAGES DO NOT MATCH THE HASHES FROM THE REQUIREMENTS FILE 296 | This error can occur when you run the `bash get_pi_requirements.sh` command in Step 1c. It occurs because the package data got corrupted while downloading. You can resolve the error by re-running the `bash get_pi_requirements.sh` command a few more times until it successfully completes without reporting that error. 297 | 298 | ### 4. Unsupported data type in custom op handler: 6488064Node number 2 (EdgeTpuDelegateForCustomOp) failed to prepare. 299 | This error occurs when trying to use a newer version of the libedgetpu library (v13.0 or greater) with an older version of TensorFlow (v2.0 or older). It can be resolved by uninstalling your current version of TensorFlow and installing the latest version of the tflite_runtime package. Issue these commands (make sure you are inside the tflite1-env virtual environment): 300 | 301 | ``` 302 | pip3 uninstall tensorflow 303 | pip3 install https://dl.google.com/coral/python/tflite_runtime-2.1.0.post1-cp37-cp37m-linux_armv7l.whl 304 | ``` 305 | 306 | (Or, if you're using Python 3.5, use `pip3 install https://dl.google.com/coral/python/tflite_runtime-2.1.0.post1-cp35-cp35m-linux_armv7l.whl` instead.) 307 | 308 | Then, re-run the TFLite detection script. It should work now! 309 | 310 | *Note: the URLs provided in these commands may change as newer versions of tflite_runtime are released. Check the [TFLite Python Quickstart page](https://www.tensorflow.org/lite/guide/python) for download URLs to the latest version of tflite_runtime.* 311 | 312 | ### 5. IndexError: list index out of range 313 | This error usually occurs when you try using an "image classification" model rather than an "object detection" model. Image classification models apply a single label to an image, while object detection models locate and label multiple objects in an image. The code in this repository is written for object detection models. 314 | 315 | Many people run in to this error when using models from Teachable Machine. This is because Teachable Machine creates image classification models rather than object detection models. To create an object detection model for TensorFow Lite, you'll have to follow the guide in this repository. 316 | 317 | If you'd like to see how to use an image classification model on the Raspberry Pi, please see this example: 318 | https://github.com/tensorflow/examples/tree/master/lite/examples/image_classification/raspberry_pi 319 | -------------------------------------------------------------------------------- /deploy_guides/Windows_TFLite_Guide.md: -------------------------------------------------------------------------------- 1 | # How to Run TensorFlow Lite Models on Windows 2 | This guide shows how to set up a TensorFlow Lite Runtime environment on a Windows PC. We'll use [Anaconda](https://www.anaconda.com/) to create a Python environment to install the TFLite Runtime in. It's easy! 3 | 4 | ## Step 1. Download and Install Anaconda 5 | First, install [Anaconda](https://www.anaconda.com/), which is a Python environment manager that greatly simplifies Python package management and deployment. Anaconda allows you to create Python virtual environments on your PC without interfering with existing installations of Python. Go to the [Anaconda Downloads page](https://www.anaconda.com/products/distribution) and click the Download button. 6 | 7 | When the download finishes, open the downloaded .exe file and step through the installation wizard. Use the default install options. 8 | 9 | ## Step 2. Set Up Virtual Environment and Directory 10 | Go to the Start Menu, search for "Anaconda Command Prompt", and click it to open up a command terminal. We'll create a folder called `tflite1` directly in the C: drive. (You can use any other folder location you like, just make sure to modify the commands below to use the correct file paths.) Create the folder and move into it by issuing the following commands in the terminal: 11 | 12 | ``` 13 | mkdir C:\tflite1 14 | cd C:\tflite1 15 | ``` 16 | 17 | Next, create a Python 3.9 virtual environment by issuing: 18 | 19 | ``` 20 | conda create --name tflite1-env python=3.9 21 | ``` 22 | 23 | Enter "y" when it asks if you want to proceed. Activate the environment and install the required packages by issuing the commands below. We'll install TensorFlow, OpenCV, and a downgraded version of protobuf. TensorFlow is a pretty big download (about 450MB), so it will take a while. 24 | 25 | ``` 26 | conda activate tflite1-env 27 | pip install tensorflow opencv-python protobuf==3.20.* 28 | ``` 29 | 30 | Download the detection scripts from this repository by issuing: 31 | 32 | ``` 33 | curl https://raw.githubusercontent.com/EdjeElectronics/TensorFlow-Lite-Object-Detection-on-Android-and-Raspberry-Pi/master/TFLite_detection_image.py --output TFLite_detection_image.py 34 | curl https://raw.githubusercontent.com/EdjeElectronics/TensorFlow-Lite-Object-Detection-on-Android-and-Raspberry-Pi/master/TFLite_detection_video.py --output TFLite_detection_video.py 35 | curl https://raw.githubusercontent.com/EdjeElectronics/TensorFlow-Lite-Object-Detection-on-Android-and-Raspberry-Pi/master/TFLite_detection_webcam.py --output TFLite_detection_webcam.py 36 | curl https://raw.githubusercontent.com/EdjeElectronics/TensorFlow-Lite-Object-Detection-on-Android-and-Raspberry-Pi/master/TFLite_detection_stream.py --output TFLite_detection_stream.py 37 | ``` 38 | 39 | ## Step 3. Move TFLite Model into Directory 40 | Next, take the custom TFLite model that was trained and downloaded from the Colab notebook and move it into the C:\tflite1 directory. If you downloaded it from Colab, it should be in a file called `custom_model_lite.zip`. (If you haven't trained a model yet and just want to test one out, download my "change counter" model by clicking this [Dropbox link](https://www.dropbox.com/scl/fi/4fk8ls8s03c94g6sb3ngo/custom_model_lite.zip?rlkey=zqda21sowk0hrw6i3f2dgbsyy&dl=0).) Move that file to the C:\tflite1 directory. Once it's moved, unzip it using: 41 | 42 | ``` 43 | tar -xf custom_model_lite.zip 44 | ``` 45 | 46 | At this point, you should have a folder at C:\tflite1\custom_model_lite which contains at least a `detect.tflite` and `labelmap.txt` file. 47 | 48 | ## Step 4. Run TensorFlow Lite Model! 49 | Alright! Now that everything is set up, running the TFLite model is easy. Just call one of the detection scripts and point it at your model folder with the `--modeldir` option. For example, to run your `custom_model_lite` model on a webcam, issue: 50 | 51 | ``` 52 | python TFLite_detection_webcam.py --modeldir=custom_model_lite 53 | ``` 54 | 55 | A window will appear showing detection results drawn on the live webcam feed. For more information on how to use the detection scripts, please see [Step 3 in the main README page](https://github.com/EdjeElectronics/TensorFlow-Lite-Object-Detection-on-Android-and-Raspberry-Pi#step-3-run-tensorflow-lite-models). 56 | 57 | Have fun using TensorFlow Lite! Stay tuned for more examples on how to build cool applications around your model. 58 | -------------------------------------------------------------------------------- /doc/BSR_demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EdjeElectronics/TensorFlow-Lite-Object-Detection-on-Android-and-Raspberry-Pi/f7f1fdec0f3e48161d598f99bf4657adbea944e8/doc/BSR_demo.gif -------------------------------------------------------------------------------- /doc/BSR_directory1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EdjeElectronics/TensorFlow-Lite-Object-Detection-on-Android-and-Raspberry-Pi/f7f1fdec0f3e48161d598f99bf4657adbea944e8/doc/BSR_directory1.png -------------------------------------------------------------------------------- /doc/Coral_and_EdgeTPU2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EdjeElectronics/TensorFlow-Lite-Object-Detection-on-Android-and-Raspberry-Pi/f7f1fdec0f3e48161d598f99bf4657adbea944e8/doc/Coral_and_EdgeTPU2.png -------------------------------------------------------------------------------- /doc/MSYS_window.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EdjeElectronics/TensorFlow-Lite-Object-Detection-on-Android-and-Raspberry-Pi/f7f1fdec0f3e48161d598f99bf4657adbea944e8/doc/MSYS_window.png -------------------------------------------------------------------------------- /doc/TFL_download_links.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EdjeElectronics/TensorFlow-Lite-Object-Detection-on-Android-and-Raspberry-Pi/f7f1fdec0f3e48161d598f99bf4657adbea944e8/doc/TFL_download_links.png -------------------------------------------------------------------------------- /doc/TFLite-vs-EdgeTPU.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EdjeElectronics/TensorFlow-Lite-Object-Detection-on-Android-and-Raspberry-Pi/f7f1fdec0f3e48161d598f99bf4657adbea944e8/doc/TFLite-vs-EdgeTPU.gif -------------------------------------------------------------------------------- /doc/YouTube_video1.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EdjeElectronics/TensorFlow-Lite-Object-Detection-on-Android-and-Raspberry-Pi/f7f1fdec0f3e48161d598f99bf4657adbea944e8/doc/YouTube_video1.JPG -------------------------------------------------------------------------------- /doc/YouTube_video2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EdjeElectronics/TensorFlow-Lite-Object-Detection-on-Android-and-Raspberry-Pi/f7f1fdec0f3e48161d598f99bf4657adbea944e8/doc/YouTube_video2.png -------------------------------------------------------------------------------- /doc/calculate-mAP-demo1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EdjeElectronics/TensorFlow-Lite-Object-Detection-on-Android-and-Raspberry-Pi/f7f1fdec0f3e48161d598f99bf4657adbea944e8/doc/calculate-mAP-demo1.gif -------------------------------------------------------------------------------- /doc/camera_enabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EdjeElectronics/TensorFlow-Lite-Object-Detection-on-Android-and-Raspberry-Pi/f7f1fdec0f3e48161d598f99bf4657adbea944e8/doc/camera_enabled.png -------------------------------------------------------------------------------- /doc/colab_upload_button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EdjeElectronics/TensorFlow-Lite-Object-Detection-on-Android-and-Raspberry-Pi/f7f1fdec0f3e48161d598f99bf4657adbea944e8/doc/colab_upload_button.png -------------------------------------------------------------------------------- /doc/labeled_image_example2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EdjeElectronics/TensorFlow-Lite-Object-Detection-on-Android-and-Raspberry-Pi/f7f1fdec0f3e48161d598f99bf4657adbea944e8/doc/labeled_image_example2.png -------------------------------------------------------------------------------- /doc/labeled_image_examples.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EdjeElectronics/TensorFlow-Lite-Object-Detection-on-Android-and-Raspberry-Pi/f7f1fdec0f3e48161d598f99bf4657adbea944e8/doc/labeled_image_examples.png -------------------------------------------------------------------------------- /doc/labelmap_example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EdjeElectronics/TensorFlow-Lite-Object-Detection-on-Android-and-Raspberry-Pi/f7f1fdec0f3e48161d598f99bf4657adbea944e8/doc/labelmap_example.png -------------------------------------------------------------------------------- /doc/local_training_guide.md: -------------------------------------------------------------------------------- 1 | # TensorFlow-Lite-Object-Detection-on-Android-and-Raspberry-Pi 2 | A guide showing how to train TensorFlow Lite object detection models on your local PC and then run them on Android, the Raspberry Pi, and more! 3 | 4 |

5 | 6 |

7 | 8 | **Important note: This guide is a bit outdated and has been replaced by the [Google Colab notebook](https://colab.research.google.com/github/EdjeElectronics/TensorFlow-Lite-Object-Detection-on-Android-and-Raspberry-Pi/blob/master/Train_TFLite2_Object_Detction_Model.ipynb) I wrote for training TFLite models using Google's virtual Colab machines. The steps in this guide worked when I wrote it 3 years ago, but I haven't tested it since then. I only recommend using this older guide if you have a strong reason for needing to train your TFLite model locally on your PC. Proceed at your own risk!** 9 | 10 | ## Introduction 11 | This guide provides step-by-step instructions for how set up a TensorFlow environment on your PC, and then use it to train, export, and deploy a custom TensorFlow Lite Object Detection model. Once you've completed the steps in this guide, you'll have a TFLite model that you can deploy using the inferencing scripts from this repository. 12 | 13 | ### A Note on Versions 14 | I used TensorFlow v1.13 while creating this guide, because TF v1.13 is a stable version that has great support from Anaconda. 15 | 16 | The TensorFlow team is always hard at work releasing updated versions of TensorFlow. I recommend picking one version and sticking with it for all your TensorFlow projects. Every part of this guide should work with newer or older versions, but you may need to use different versions of the tools needed to run or build TensorFlow (CUDA, cuDNN, bazel, etc). Google has provided a list of build configurations for [Linux](https://www.tensorflow.org/install/source#linux), [macOS](https://www.tensorflow.org/install/source#macos), and [Windows](https://www.tensorflow.org/install/source_windows#tested_build_configurations) that show which tool versions were used to build and run each version of TensorFlow. 17 | 18 | ## How to Train, Convert, and Run Custom TensorFlow Lite Object Detection Models on Windows 10 19 | This guide gives instructions for training and deploying your own custom TensorFlow Lite object detection model on a Windows 10 PC. The guide is based off the [tutorial in the TensorFlow Object Detection repository](https://github.com/tensorflow/models/blob/master/research/object_detection/g3doc/running_on_mobile_tensorflowlite.md), but it gives more detailed instructions and is written specifically for Windows. (It will work on Linux too with some minor changes, which I leave as an exercise for the Linux user.) 20 | 21 | There are three primary steps to training and deploying a TensorFlow Lite model: 22 | 1. [Train a quantized SSD-MobileNet model using TensorFlow, and export frozen graph for TensorFlow Lite](#step-1-train-quantized-ssd-mobilenet-model-and-export-frozen-tensorflow-lite-graph) 23 | 2. [Build TensorFlow from source on your PC](#step-2-build-tensorflow-from-source) 24 | 3. [Use TensorFlow Lite Optimizing Converter (TOCO) to create optimzed TensorFlow Lite model](#step-3-use-toco-to-create-optimzed-tensorflow-lite-model-create-label-map-run-model) 25 | 26 | This portion is a continuation of my previous guide: [How To Train an Object Detection Model Using TensorFlow on Windows 10](https://github.com/EdjeElectronics/TensorFlow-Object-Detection-API-Tutorial-Train-Multiple-Objects-Windows-10). I'll assume you have already set up TensorFlow to train a custom object detection model as described in that guide, including: 27 | * Setting up an Anaconda virtual environment for training 28 | * Setting up TensorFlow directory structure 29 | * Gathering and labeling training images 30 | * Preparing training data (generating TFRecords and label map) 31 | 32 | This tutorial uses the same Anaconda virtual environment, files, and directory structure that was set up in the previous one. 33 | 34 | Through the course of the guide, I'll use a bird, squirrel, and raccoon detector model I've been working on as an example. The intent of this detection model is to watch a bird feeder, and record videos of birds while triggering an alarm if a squirrel or raccoon is stealing from it! I'll show the steps needed to train, convert, and run a quantized TensorFlow Lite version of the bird/squirrel/raccoon detector. 35 | 36 | Parts 2 and 3 of this guide will go on to show how to deploy this newly trained TensorFlow Lite model on the Raspberry Pi or an Android device. If you're not feeling up to training and converting your own TensorFlow Lite model, you can skip Part 1 and use my custom-trained TFLite BSR detection model [(which you can download from Dropbox here)](https://www.dropbox.com/s/cpaon1j1r1yzflx/BirdSquirrelRaccoon_TFLite_model.zip?dl=0) or use the [TF Lite starter detection model](https://storage.googleapis.com/download.tensorflow.org/models/tflite/coco_ssd_mobilenet_v1_1.0_quant_2018_06_29.zip) (taken from https://www.tensorflow.org/lite/models/object_detection/overview) for Part 2 or Part 3. 37 | 38 | ### Step 1: Train Quantized SSD-MobileNet Model and Export Frozen TensorFlow Lite Graph 39 | First, we’ll use transfer learning to train a “quantized” SSD-MobileNet model. Quantized models use 8-bit integer values instead of 32-bit floating values within the neural network, allowing them to run much more efficiently on GPUs or specialized TPUs (TensorFlow Processing Units). 40 | 41 | You can also use a standard SSD-MobileNet model (V1 or V2), but it will not run quite as fast as the quantized model. Also, you will not be able to run it on the Google Coral TPU Accelerator. If you’re using an SSD-MobileNet model that has already been trained, you can skip to [Step 1d](#step-1d-export-frozen-inference-graph-for-tensorflow-lite) of this guide. 42 | 43 | **If you get any errors during this process, please look at the [FAQ section](#frequently-asked-questions-and-common-errors) at the bottom of this guide! It gives solutions to common errors that occur.** 44 | 45 | As I mentioned prevoiusly, this guide assumes you have already followed my [previous TensorFlow tutorial](https://github.com/EdjeElectronics/TensorFlow-Object-Detection-API-Tutorial-Train-Multiple-Objects-Windows-10) and set up the Anaconda virtual environment and full directory structure needed for using the TensorFlow Object Detection API. If you've done so, you should have a folder at C:\tensorflow1\models\research\object_detection that has everything needed for training. (If you used a different base folder name than "tensorflow1", that's fine - just make sure you continue to use that name throughout this guide.) 46 | 47 | Here's what your \object_detection folder should look like: 48 |

49 | 50 |

51 | 52 | If you don't have this folder, please go to my [previous tutorial](https://github.com/EdjeElectronics/TensorFlow-Object-Detection-API-Tutorial-Train-Multiple-Objects-Windows-10) and work through at least Steps 1 and 2. If you'd like to train your own model to detect custom objects, you'll also need to work through Steps 3, 4, and 5. If you don't want to train your own model but want to practice the process for converting a model to TensorFlow Lite, you can download the quantized MobileNet-SSD model (see next paragraph) and then skip to [Step 1d](#step-1d-export-frozen-inference-graph-for-tensorflow-lite). 53 | 54 | #### Step 1a. Download and extract quantized SSD-MobileNet model 55 | Google provides several quantized object detection models in their [detection model zoo](https://github.com/tensorflow/models/blob/master/research/object_detection/g3doc/tf1_detection_zoo.md). This tutorial will use the SSD-MobileNet-V2-Quantized-COCO model. Download the model [here](http://download.tensorflow.org/models/object_detection/ssd_mobilenet_v2_quantized_300x300_coco_2019_01_03.tar.gz). **Note: TensorFlow Lite does NOT support RCNN models such as Faster-RCNN! It only supports SSD models.** 56 | 57 | Move the downloaded .tar.gz file to the C:\tensorflow1\models\research\object_detection folder. (Henceforth, this folder will be referred to as the “\object_detection” folder.) Unzip the .tar.gz file using a file archiver like WinZip or 7-Zip. After the file has been fully unzipped, you should have a folder called "ssd_mobilenet_v2_quantized_300x300_coco_2019_01_03" within the \object_detection folder. 58 | 59 | #### Step 1b. Configure training 60 | If you're training your own TensorFlow Lite model, make sure the following items from my [previous guide](https://github.com/EdjeElectronics/TensorFlow-Object-Detection-API-Tutorial-Train-Multiple-Objects-Windows-10) have been completed: 61 | * Train and test images and their XML label files are placed in the \object_detection\images\train and \object_detection\images\test folders 62 | * train_labels.csv and test_labels.csv have been generated and are located in the \object_detection\images folder 63 | * train.record and test.record have been generated and are located in the \object_detection folder 64 | * labelmap.pbtxt file has been created and is located in the \object_detection\training folder 65 | * proto files in \object_detection\protos have been generated 66 | 67 | If you have any questions about these files or don’t know how to generate them, [Steps 2, 3, 4, and 5 of my previous tutorial](https://github.com/EdjeElectronics/TensorFlow-Object-Detection-API-Tutorial-Train-Multiple-Objects-Windows-10) show how they are all created. 68 | 69 | Copy the ssd_mobilenet_v2_quantized_300x300_coco.config file from the \object_detection\samples\configs folder to the \object_detection\training folder. Then, open the file using a text editor. 70 | 71 | Make the following changes to the ssd_mobilenet_v2_quantized_300x300_coco.config file. Note: The paths must be entered with single forward slashes (NOT backslashes), or TensorFlow will give a file path error when trying to train the model! Also, the paths must be in double quotation marks ( " ), not single quotation marks ( ' ). 72 | 73 | * Line 9. Change num_classes to the number of different objects you want the classifier to detect. For my bird/squirrel/raccoon detector example, there are three classes, so I set num_classes: 3 74 | 75 | * Line 141. Change batch_size: 24 to batch_size: 6 . The smaller batch size will prevent OOM (Out of Memory) errors during training. 76 | 77 | * Line 156. Change fine_tune_checkpoint to: "C:/tensorflow1/models/research/object_detection/ ssd_mobilenet_v2_quantized_300x300_coco_2019_01_03/model.ckpt" 78 | 79 | * Line 175. Change input_path to: "C:/tensorflow1/models/research/object_detection/train.record" 80 | 81 | * Line 177. Change label_map_path to: "C:/tensorflow1/models/research/object_detection/training/labelmap.pbtxt" 82 | 83 | * Line 181. Change num_examples to the number of images you have in the \images\test directory. For my bird/squirrel/raccoon detector example, there are 582 test images, so I set num_examples: 582. 84 | 85 | * Line 189. Change input_path to: "C:/tensorflow1/models/research/object_detection/test.record" 86 | 87 | * Line 191. Change label_map_path to: "C:/tensorflow1/models/research/object_detection/training/labelmap.pbtxt" 88 | 89 | Save and exit the training file after the changes have been made. 90 | 91 | #### Step 1c. Run training in Anaconda virtual environment 92 | All that's left to do is train the model! First, move the “train.py” file from the \object_detection\legacy folder into the main \object_detection folder. (See the [FAQ](#frequently-asked-questions-and-common-errors) for why I am using the legacy train.py script rather than model_main.py for training.) 93 | 94 | Then, open a new Anaconda Prompt window by searching for “Anaconda Prompt” in the Start menu and clicking on it. Activate the “tensorflow1” virtual environment (which was set up in my [previous tutorial](https://github.com/EdjeElectronics/TensorFlow-Object-Detection-API-Tutorial-Train-Multiple-Objects-Windows-10)) by issuing: 95 | 96 | ``` 97 | activate tensorflow1 98 | ``` 99 | 100 | Then, set the PYTHONPATH environment variable by issuing: 101 | 102 | ``` 103 | set PYTHONPATH=C:\tensorflow1\models;C:\tensorflow1\models\research;C:\tensorflow1\models\research\slim 104 | ``` 105 | 106 | Next, change directories to the \object_detection folder: 107 | 108 | ``` 109 | cd C:\tensorflow1\models\research\object_detection 110 | ``` 111 | 112 | Finally, train the model by issuing: 113 | 114 | ``` 115 | python train.py --logtostderr –train_dir=training/ --pipeline_config_path=training/ssd_mobilenet_v2_quantized_300x300_coco.config 116 | ``` 117 | 118 | If everything was set up correctly, the model will begin training after a couple minutes of initialization. 119 | 120 |

121 | 122 |

123 | 124 | Allow the model to train until the loss consistently drops below 2. For my bird/squirrel/raccoon detector model, this took about 9000 steps, or 8 hours of training. (Time will vary depending on how powerful your CPU and GPU are. Please see [Step 6 of my previous tutorial](https://github.com/EdjeElectronics/TensorFlow-Object-Detection-API-Tutorial-Train-Multiple-Objects-Windows-10/blob/master/README.md#6-run-the-training) for more information on training and an explanation of how to view the progress of the training job using TensorBoard.) 125 | 126 | Once training is complete (i.e. the loss has consistently dropped below 2), press Ctrl+C to stop training. The latest checkpoint will be saved in the \object_detection\training folder, and we will use that checkpoint to export the frozen TensorFlow Lite graph. Take note of the checkpoint number of the model.ckpt file in the training folder (i.e. model.ckpt-XXXX), as it will be used later. 127 | 128 | #### Step 1d. Export frozen inference graph for TensorFlow Lite 129 | Now that training has finished, the model can be exported for conversion to TensorFlow Lite using the export_tflite_ssd_graph.py script. First, create a folder in \object_detection called “TFLite_model” by issuing: 130 | 131 | ``` 132 | mkdir TFLite_model 133 | ``` 134 | 135 | Next, let’s set up some environment variables so the commands are easier to type out. Issue the following commands in Anaconda Prompt. (Note, the XXXX in the second command should be replaced with the highest-numbered model.ckpt file in the \object_detection\training folder.) 136 | 137 | ``` 138 | set CONFIG_FILE=C:\\tensorflow1\models\research\object_detection\training\ssd_mobilenet_v2_quantized_300x300_coco.config 139 | set CHECKPOINT_PATH=C:\\tensorflow1\models\research\object_detection\training\model.ckpt-XXXX 140 | set OUTPUT_DIR=C:\\tensorflow1\models\research\object_detection\TFLite_model 141 | ``` 142 | 143 | Now that those are set up, issue this command to export the model for TensorFlow Lite: 144 | 145 | ``` 146 | python export_tflite_ssd_graph.py --pipeline_config_path=%CONFIG_FILE% --trained_checkpoint_prefix=%CHECKPOINT_PATH% --output_directory=%OUTPUT_DIR% --add_postprocessing_op=true 147 | ``` 148 | 149 | After the command has executed, there should be two new files in the \object_detection\TFLite_model folder: tflite_graph.pb and tflite_graph.pbtxt. 150 | 151 | That’s it! The new inference graph has been trained and exported. This inference graph's architecture and network operations are compatible with TensorFlow Lite's framework. However, the graph still needs to be converted to an actual TensorFlow Lite model. We'll do that in Step 3. First, we have to build TensorFlow from source. On to Step 2! 152 | 153 | ### Step 2. Build TensorFlow From Source 154 | To convert the frozen graph we just exported into a model that can be used by TensorFlow Lite, it has to be run through the TensorFlow Lite Optimizing Converter (TOCO). Unfortunately, to use TOCO, we have to build TensorFlow from source on our computer. To do this, we’ll create a separate Anaconda virtual environment for building TensorFlow. 155 | 156 | This part of the tutorial breaks down step-by-step how to build TensorFlow from source on your Windows PC. It follows the [Build TensorFlow From Source on Windows](https://www.tensorflow.org/install/source_windows) instructions given on the official TensorFlow website, with some slight modifications. 157 | 158 | This guide will show how to build either the CPU-only version of TensorFlow or the GPU-enabled version of TensorFlow v1.13. If you would like to build a version other than TF v1.13, you can still use this guide, but check the [build configuration list](https://www.tensorflow.org/install/source_windows#tested_build_configurations) and make sure you use the correct package versions. 159 | 160 | **If you are only building TensorFlow to convert a TensorFlow Lite object detection model, I recommend building the CPU-only version!** It takes very little computational effort to export the model, so your CPU can do it just fine without help from your GPU. If you’d like to build the GPU-enabled version anyway, then you need to have the appropriate version of CUDA and cuDNN installed. [The TensorFlow installation guide](https://www.tensorflow.org/install/gpu#windows_setup) explains how to install CUDA and cuDNN. Check the [build configuration list](https://www.tensorflow.org/install/source_windows#tested_build_configurations) to see which versions of CUDA and cuDNN are compatible with which versions of TensorFlow. 161 | 162 | **If you get any errors during this process, please look at the [FAQ section](frequently-asked-questions-and-common-errors) at the bottom of this guide! It gives solutions to common errors that occur.** 163 | 164 | #### Step 2a. Install MSYS2 165 | MSYS2 has some binary tools needed for building TensorFlow. It also automatically converts Windows-style directory paths to Linux-style paths when using Bazel. The Bazel build won’t work without MSYS2 installed! 166 | 167 | First, install MSYS2 by following the instructions on the [MSYS2 website](https://www.msys2.org/). Download the msys2-x86_64 executable file and run it. Use the default options for installation. After installing, open MSYS2 and issue: 168 | 169 | ``` 170 | pacman -Syu 171 | ``` 172 | 173 | 174 | 175 | After it's completed, close the window, re-open it, and then issue the following two commands: 176 | 177 | ``` 178 | pacman -Su 179 | pacman -S patch unzip 180 | ``` 181 | 182 |

183 | 184 |

185 | 186 | This updates MSYS2’s package manager and downloads the patch and unzip packages. Now, close the MSYS2 window. We'll add the MSYS2 binary to the PATH environment variable in Step 2c. 187 | 188 | #### Step 2b. Install Visual C++ Build Tools 2015 189 | Install Microsoft Build Tools 2015 and Microsoft Visual C++ 2015 Redistributable by visiting the [Visual Studio older downloads](https://visualstudio.microsoft.com/vs/older-downloads/) page. Click the “Redistributables and Build Tools” dropdown at the bottom of the list. Download and install the following two packages: 190 | 191 | * **Microsoft Build Tools 2015 Update 3** - Use the default installation options in the install wizard. Once you begin installing, it goes through a fairly large download, so it will take a while if you have a slow internet connection. It may give you some warnings saying build tools or redistributables have already been installed. If so, that's fine; just click through them. 192 | * **Microsoft Visual C++ 2015 Redistributable Update 3** – This may give you an error saying the redistributable has already been installed. If so, that’s fine. 193 | 194 | Restart your PC after installation has finished. 195 | 196 | #### Step 2c. Update Anaconda and create tensorflow-build environment 197 | Now that the Visual Studio tools are installed and your PC is freshly restarted, open a new Anaconda Prompt window. First, update Anaconda to make sure its package list is up to date. In the Anaconda Prompt window, issue these two commands: 198 | 199 | ``` 200 | conda update -n base -c defaults conda 201 | conda update --all 202 | ``` 203 | 204 | The update process may take up to an hour, depending on how it's been since you installed or updated Anaconda. Next, create a new Anaconda virtual environment called “tensorflow-build”. We’ll work in this environment for the rest of the build process. Create and activate the environment by issuing: 205 | 206 | ``` 207 | conda create -n tensorflow-build pip python=3.6 208 | conda activate tensorflow-build 209 | ``` 210 | 211 | After the environment is activated, you should see (tensorflow-build) before the active path in the command window. 212 | 213 | Update pip by issuing: 214 | 215 | ``` 216 | python -m pip install --upgrade pip 217 | ``` 218 | 219 | We'll use Anaconda's git package to download the TensorFlow repository, so install git using: 220 | 221 | ``` 222 | conda install -c anaconda git 223 | ``` 224 | 225 | Next, add the MSYS2 binaries to this environment's PATH variable by issuing: 226 | 227 | ``` 228 | set PATH=%PATH%;C:\msys64\usr\bin 229 | ``` 230 | 231 | (If MSYS2 is installed in a different location than C:\msys64, use that location instead.) You’ll have to re-issue this PATH command if you ever close and re-open the Anaconda Prompt window. 232 | 233 | #### Step 2d. Download Bazel and Python package dependencies 234 | Next, we’ll install Bazel and some other Python packages that are used for building TensorFlow. Install the necessary Python packages by issuing: 235 | 236 | ``` 237 | pip install six numpy wheel 238 | pip install keras_applications==1.0.6 --no-deps 239 | pip install keras_preprocessing==1.0.5 --no-deps 240 | ``` 241 | 242 | Then install Bazel v0.21.0 by issuing the following command. (If you are building a version of TensorFlow other than v1.13, you may need to use a different version of Bazel.) 243 | 244 | ``` 245 | conda install -c conda-forge bazel=0.21.0 246 | ``` 247 | 248 | #### Step 2d. Download TensorFlow source and configure build 249 | Time to download TensorFlow’s source code from GitHub! Issue the following commands to create a new folder directly in C:\ called “tensorflow-build” and cd into it: 250 | 251 | ``` 252 | mkdir C:\tensorflow-build 253 | cd C:\tensorflow-build 254 | ``` 255 | 256 | Then, clone the TensorFlow repository and cd into it by issuing: 257 | 258 | ``` 259 | git clone https://github.com/tensorflow/tensorflow.git 260 | cd tensorflow 261 | ``` 262 | 263 | Next, check out the branch for TensorFlow v1.13: 264 | 265 | ``` 266 | git checkout r1.13 267 | ``` 268 | 269 | The version you check out should match the TensorFlow version you used to train your model in [Step 1](#step-1-train-quantized-ssd-mobilenet-model-and-export-frozen-tensorflow-lite-graph). If you used a different version than TF v1.13, then replace "1.13" with the version you used. See the [FAQs section](#how-do-i-check-which-tensorflow-version-i-used-to-train-my-detection-model) for instructions on how to check the TensorFlow version you used for training. 270 | 271 | Next, we’ll configure the TensorFlow build using the configure.py script. From the C:\tensorflow-build\tensorflow directory, issue: 272 | 273 | ``` 274 | python ./configure.py 275 | ``` 276 | 277 | This will initiate a Bazel session. As I mentioned before, you can build either the CPU-only version of TensorFlow or the GPU-enabled version of TensorFlow. If you're only using this TensorFlow build to convert your TensorFlow Lite model, **I recommend building the CPU-only version**. If you’d still like to build the GPU-enabled version for some other reason, then you need to have the appropriate version of CUDA and cuDNN installed. This guide doesn't cover building the GPU-enabled version of TensorFlow, but you can try following the official build instructions on the [TensorFlow website](https://www.tensorflow.org/install/source_windows). 278 | 279 | Here’s what the configuration session will look like if you are building for CPU only. Basically, press Enter to select the default option for each question. 280 | 281 | ``` 282 | You have bazel 0.21.0- (@non-git) installed. 283 | 284 | Please specify the location of python. [Default is C:\ProgramData\Anaconda3\envs\tensorflow-build\python.exe]: 285 | 286 | Found possible Python library paths: 287 | 288 | C:\ProgramData\Anaconda3\envs\tensorflow-build\lib\site-packages 289 | 290 | Please input the desired Python library path to use. Default is [C:\ProgramData\Anaconda3\envs\tensorflow-build\lib\site-packages] 291 | 292 | Do you wish to build TensorFlow with XLA JIT support? [y/N]: N 293 | No XLA JIT support will be enabled for TensorFlow. 294 | 295 | Do you wish to build TensorFlow with ROCm support? [y/N]: N 296 | No ROCm support will be enabled for TensorFlow. 297 | 298 | Do you wish to build TensorFlow with CUDA support? [y/N]: N 299 | No CUDA support will be enabled for TensorFlow. 300 | ``` 301 | 302 | Once the configuration is finished, TensorFlow is ready to be bulit! 303 | 304 | #### Step 2e. Build TensorFlow package 305 | Next, use Bazel to create the package builder for TensorFlow. To create the CPU-only version, issue the following command. The build process took about 70 minutes on my computer. 306 | 307 | ``` 308 | bazel build --config=opt //tensorflow/tools/pip_package:build_pip_package 309 | ``` 310 | 311 | Now that the package builder has been created, let’s use it to build the actual TensorFlow wheel file. Issue the following command (it took about 5 minutes to complete on my computer): 312 | 313 | ``` 314 | bazel-bin\tensorflow\tools\pip_package\build_pip_package C:/tmp/tensorflow_pkg 315 | ``` 316 | 317 | This creates the wheel file and places it in C:\tmp\tensorflow_pkg. 318 | 319 | #### Step 2f. Install TensorFlow and test it out! 320 | TensorFlow is finally ready to be installed! Open File Explorer and browse to the C:\tmp\tensorflow_pkg folder. Copy the full filename of the .whl file, and paste it in the following command: 321 | 322 | ``` 323 | pip3 install C:/tmp/tensorflow_pkg/ 324 | ``` 325 | 326 | That's it! TensorFlow is installed! Let's make sure it installed correctly by opening a Python shell: 327 | 328 | ``` 329 | python 330 | ``` 331 | 332 | Once the shell is opened, issue these commands: 333 | 334 | ``` 335 | >>> import tensorflow as tf 336 | >>> tf.__version__ 337 | ``` 338 | 339 | If everything was installed properly, it will respond with the installed version of TensorFlow. Note: You may get some deprecation warnings after the "import tensorflow as tf" command. As long as they are warnings and not actual errors, you can ignore them! Exit the shell by issuing: 340 | 341 | ``` 342 | exit() 343 | ``` 344 | 345 | With TensorFlow installed, we can finally convert our trained model into a TensorFlow Lite model. On to the last step: Step 3! 346 | 347 | ### Step 3. Use TOCO to Create Optimzed TensorFlow Lite Model, Create Label Map, Run Model 348 | Although we've already exported a frozen graph of our detection model for TensorFlow Lite, we still need run it through the TensorFlow Lite Optimizing Converter (TOCO) before it will work with the TensorFlow Lite interpreter. TOCO converts models into an optimized FlatBuffer format that allows them to run efficiently on TensorFlow Lite. We also need to create a new label map before running the model. 349 | 350 | #### Step 3a. Create optimized TensorFlow Lite model 351 | First, we’ll run the model through TOCO to create an optimzed TensorFLow Lite model. The TOCO tool lives deep in the C:\tensorflow-build directory, and it will be run from the “tensorflow-build” Anaconda virtual environment that we created and used during Step 2. Meanwhile, the model we trained in Step 1 lives inside the C:\tensorflow1\models\research\object_detection\TFLite_model directory. We’ll create an environment variable called OUTPUT_DIR that points at the correct model directory to make it easier to enter the TOCO command. 352 | 353 | If you don't already have an Anaconda Prompt window open with the "tensorflow-build" environment active and working in C:\tensorflow-build, open a new Anaconda Prompt window and issue: 354 | 355 | ``` 356 | activate tensorflow-build 357 | cd C:\tensorflow-build 358 | ``` 359 | 360 | Create the OUTPUT_DIR environment variable by issuing: 361 | 362 | ``` 363 | set OUTPUT_DIR=C:\\tensorflow1\models\research\object_detection\TFLite_model 364 | ``` 365 | 366 | Next, use Bazel to run the model through the TOCO tool by issuing this command: 367 | 368 | ``` 369 | bazel run --config=opt tensorflow/lite/toco:toco -- --input_file=%OUTPUT_DIR%/tflite_graph.pb --output_file=%OUTPUT_DIR%/detect.tflite --input_shapes=1,300,300,3 --input_arrays=normalized_input_image_tensor --output_arrays=TFLite_Detection_PostProcess,TFLite_Detection_PostProcess:1,TFLite_Detection_PostProcess:2,TFLite_Detection_PostProcess:3 --inference_type=QUANTIZED_UINT8 --mean_values=128 --std_values=128 --change_concat_input_ranges=false --allow_custom_ops 370 | ``` 371 | 372 | Note: If you are using a floating, non-quantized SSD model (e.g. the ssdlite_mobilenet_v2_coco model rather than the ssd_mobilenet_v2_quantized_coco model), the Bazel TOCO command must be modified slightly: 373 | 374 | ``` 375 | bazel run --config=opt tensorflow/lite/toco:toco -- --input_file=$OUTPUT_DIR/tflite_graph.pb --output_file=$OUTPUT_DIR/detect.tflite --input_shapes=1,300,300,3 --input_arrays=normalized_input_image_tensor --output_arrays=TFLite_Detection_PostProcess,TFLite_Detection_PostProcess:1,TFLite_Detection_PostProcess:2,TFLite_Detection_PostProcess:3 --inference_type=FLOAT --allow_custom_ops 376 | ``` 377 | 378 | If you are using Linux, make sure to use the commands given in the [official TensorFlow instructions here](https://github.com/tensorflow/models/blob/master/research/object_detection/g3doc/running_on_mobile_tensorflowlite.md). I removed the ' characters from the command, because for some reason they cause errors on Windows! 379 | 380 | After the command finishes running, you should see a file called detect.tflite in the \object_detection\TFLite_model directory. This is the model that can be used with TensorFlow Lite! 381 | 382 | #### Step 3b. Create new label map 383 | For some reason, TensorFlow Lite uses a different label map format than classic TensorFlow. The classic TensorFlow label map format looks like this (you can see an example in the \object_detection\data\mscoco_label_map.pbtxt file): 384 | 385 | ``` 386 | item { 387 | name: "/m/01g317" 388 | id: 1 389 | display_name: "person" 390 | } 391 | item { 392 | name: "/m/0199g" 393 | id: 2 394 | display_name: "bicycle" 395 | } 396 | item { 397 | name: "/m/0k4j" 398 | id: 3 399 | display_name: "car" 400 | } 401 | item { 402 | name: "/m/04_sv" 403 | id: 4 404 | display_name: "motorcycle" 405 | } 406 | And so on... 407 | ``` 408 | 409 | However, the label map provided with the [example TensorFlow Lite object detection model](https://storage.googleapis.com/download.tensorflow.org/models/tflite/coco_ssd_mobilenet_v1_1.0_quant_2018_06_29.zip) looks like this: 410 | 411 | ``` 412 | person 413 | bicycle 414 | car 415 | motorcycle 416 | And so on... 417 | ``` 418 | 419 | Basically, rather than explicitly stating the name and ID number for each class like the classic TensorFlow label map format does, the TensorFlow Lite format just lists each class. To stay consistent with the example provided by Google, I’m going to stick with the TensorFlow Lite label map format for this guide. 420 | 421 | Thus, we need to create a new label map that matches the TensorFlow Lite style. Open a text editor and list each class in order of their class number. Then, save the file as “labelmap.txt” in the TFLite_model folder. As an example, here's what the labelmap.txt file for my bird/squirrel/raccoon detector looks like: 422 | 423 |

424 | 425 |

426 | 427 | Now we’re ready to run the model! 428 | 429 | #### Step 3c. Run the TensorFlow Lite model! 430 | I wrote three Python scripts to run the TensorFlow Lite object detection model on an image, video, or webcam feed: `TFLite_detection_image.py`, `TFLite_detection_video.py`, and `TFLite_detection_webcam.py`. The scripts are based off the `label_image.py` example given in the [TensorFlow Lite examples GitHub repository](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/lite/examples/python/label_image.py). 431 | 432 | We’ll download the Python scripts directly from this repository. First, install wget for Anaconda by issuing: 433 | 434 | ``` 435 | conda install -c menpo wget 436 | ``` 437 | 438 | Once it's installed, download the scripts by issuing: 439 | 440 | ``` 441 | wget https://raw.githubusercontent.com/EdjeElectronics/TensorFlow-Lite-Object-Detection-on-Android-and-Raspberry-Pi/master/TFLite_detection_image.py --no-check-certificate 442 | wget https://raw.githubusercontent.com/EdjeElectronics/TensorFlow-Lite-Object-Detection-on-Android-and-Raspberry-Pi/master/TFLite_detection_video.py --no-check-certificate 443 | wget https://raw.githubusercontent.com/EdjeElectronics/TensorFlow-Lite-Object-Detection-on-Android-and-Raspberry-Pi/master/TFLite_detection_webcam.py --no-check-certificate 444 | ``` 445 | 446 | Instructions for running the webcam, video, and image scripts are given on the main README page of this repository (link to be added). Go check them out to see how to deploy your newly trained model! 447 | 448 | ## Frequently Asked Questions and Common Errors 449 | 450 | #### Why does this guide use train.py rather than model_main.py for training? 451 | This guide uses "train.py" to run training on the TFLite detection model. The train.py script is deprecated, but the model_main.py script that replaced it doesn't log training progress by default, and it requires pycocotools to be installed. Using model_main.py requires a few extra setup steps, and I want to keep this guide as simple as possible. Since there are no major differences between train.py and model_main.py that will affect training ([see TensorFlow Issue #6100](https://github.com/tensorflow/models/issues/6100)), I use train.py for this guide. 452 | 453 | #### How do I check which TensorFlow version I used to train my detection model? 454 | Here’s how you can check the version of TensorFlow you used for training. 455 | 456 | 1. Open a new Anaconda Prompt window and issue `activate tensorflow1` (or whichever environment name you used) 457 | 2. Open a python shell by issuing `python` 458 | 3. Within the Python shell, import TensorFlow by issuing `import tensorflow as tf` 459 | 4. Check the TensorFlow version by issuing `tf.__version__` . It will respond with the version of TensorFlow. This is the version that you used for training. 460 | 461 | #### Building TensorFlow from source 462 | In case you run into error `error C2100: illegal indirection` during TensorFlow compilation, simply edit the file `tensorflow-build\tensorflow\tensorflow\core\framework\op_kernel.h`, go to line 405, and change `reference operator*() { return (*list_)[i_]; }` to `reference operator*() const { return (*list_)[i_]; }`. Credits go to: https://github.com/tensorflow/tensorflow/issues/15925#issuecomment-499569928 463 | -------------------------------------------------------------------------------- /doc/object_detection_folder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EdjeElectronics/TensorFlow-Lite-Object-Detection-on-Android-and-Raspberry-Pi/f7f1fdec0f3e48161d598f99bf4657adbea944e8/doc/object_detection_folder.png -------------------------------------------------------------------------------- /doc/squirrels!!.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EdjeElectronics/TensorFlow-Lite-Object-Detection-on-Android-and-Raspberry-Pi/f7f1fdec0f3e48161d598f99bf4657adbea944e8/doc/squirrels!!.png -------------------------------------------------------------------------------- /doc/tflite1_folder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EdjeElectronics/TensorFlow-Lite-Object-Detection-on-Android-and-Raspberry-Pi/f7f1fdec0f3e48161d598f99bf4657adbea944e8/doc/tflite1_folder.png -------------------------------------------------------------------------------- /doc/training_in_progress.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EdjeElectronics/TensorFlow-Lite-Object-Detection-on-Android-and-Raspberry-Pi/f7f1fdec0f3e48161d598f99bf4657adbea944e8/doc/training_in_progress.png -------------------------------------------------------------------------------- /examples/ChangeCounter.py: -------------------------------------------------------------------------------- 1 | ######## Count Change Using Object Detection ######### 2 | # 3 | # Author: Evan Juras, EJ Technology Consultants (www.ejtech.io) 4 | # Date: 10/29/22 5 | # 6 | # Description: 7 | # This program uses a TFLite coin detection model to locate and identify coins in 8 | # a live camera feed. It calculates the total value of the coins in the camera's view. 9 | # (Works on US currency, but can be modified to work with coins from other countries!) 10 | 11 | # Import packages 12 | import os 13 | import argparse 14 | import cv2 15 | import numpy as np 16 | import sys 17 | import time 18 | from threading import Thread 19 | import importlib.util 20 | 21 | ### User-defined variables 22 | 23 | # Model info 24 | MODEL_NAME = 'change_counter' 25 | GRAPH_NAME = 'detect.tflite' 26 | LABELMAP_NAME = 'labelmap.txt' 27 | use_TPU = False 28 | 29 | # Program settings 30 | min_conf_threshold = 0.50 31 | resW, resH = 1280, 720 # Resolution to run camera at 32 | imW, imH = resW, resH 33 | 34 | ### Set up model parameters 35 | 36 | # Import TensorFlow libraries 37 | # If tflite_runtime is installed, import interpreter from tflite_runtime, else import from regular tensorflow 38 | # If using Coral Edge TPU, import the load_delegate library 39 | pkg = importlib.util.find_spec('tflite_runtime') 40 | if pkg: 41 | from tflite_runtime.interpreter import Interpreter 42 | if use_TPU: 43 | from tflite_runtime.interpreter import load_delegate 44 | else: 45 | from tensorflow.lite.python.interpreter import Interpreter 46 | if use_TPU: 47 | from tensorflow.lite.python.interpreter import load_delegate 48 | 49 | # If using Edge TPU, assign filename for Edge TPU model 50 | if use_TPU: 51 | # If user has specified the name of the .tflite file, use that name, otherwise use default 'edgetpu.tflite' 52 | if (GRAPH_NAME == 'detect.tflite'): 53 | GRAPH_NAME = 'edgetpu.tflite' 54 | 55 | # Get path to current working directory 56 | CWD_PATH = os.getcwd() 57 | 58 | # Path to .tflite file, which contains the model that is used for object detection 59 | PATH_TO_CKPT = os.path.join(CWD_PATH,MODEL_NAME,GRAPH_NAME) 60 | 61 | # Path to label map file 62 | PATH_TO_LABELS = os.path.join(CWD_PATH,MODEL_NAME,LABELMAP_NAME) 63 | 64 | # Load the label map 65 | with open(PATH_TO_LABELS, 'r') as f: 66 | labels = [line.strip() for line in f.readlines()] 67 | 68 | ### Load Tensorflow Lite model 69 | # If using Edge TPU, use special load_delegate argument 70 | if use_TPU: 71 | interpreter = Interpreter(model_path=PATH_TO_CKPT, 72 | experimental_delegates=[load_delegate('libedgetpu.so.1.0')]) 73 | else: 74 | interpreter = Interpreter(model_path=PATH_TO_CKPT) 75 | 76 | interpreter.allocate_tensors() 77 | 78 | # Get model details 79 | input_details = interpreter.get_input_details() 80 | output_details = interpreter.get_output_details() 81 | height = input_details[0]['shape'][1] 82 | width = input_details[0]['shape'][2] 83 | 84 | floating_model = (input_details[0]['dtype'] == np.float32) 85 | 86 | input_mean = 127.5 87 | input_std = 127.5 88 | 89 | # Check output layer name to determine if this model was created with TF2 or TF1, 90 | # because outputs are ordered differently for TF2 and TF1 models 91 | outname = output_details[0]['name'] 92 | 93 | if ('StatefulPartitionedCall' in outname): # This is a TF2 model 94 | boxes_idx, classes_idx, scores_idx = 1, 3, 0 95 | else: # This is a TF1 model 96 | boxes_idx, classes_idx, scores_idx = 0, 1, 2 97 | 98 | # Initialize camera 99 | cap = cv2.VideoCapture(0) 100 | ret = cap.set(3, resW) 101 | ret = cap.set(4, resH) 102 | 103 | # Initialize frame rate calculation 104 | frame_rate_calc = 1 105 | freq = cv2.getTickFrequency() 106 | 107 | ### Continuously process frames from camera 108 | while True: 109 | 110 | # Start timer (for calculating frame rate) 111 | t1 = cv2.getTickCount() 112 | 113 | # Reset coin value count for this frame 114 | total_coin_value = 0 115 | 116 | # Grab frame from camera 117 | hasFrame, frame1 = cap.read() 118 | 119 | # Acquire frame and resize to input shape expected by model [1xHxWx3] 120 | frame = frame1.copy() 121 | frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) 122 | frame_resized = cv2.resize(frame_rgb, (width, height)) 123 | input_data = np.expand_dims(frame_resized, axis=0) 124 | 125 | # Normalize pixel values if using a floating model (i.e. if model is non-quantized) 126 | if floating_model: 127 | input_data = (np.float32(input_data) - input_mean) / input_std 128 | 129 | # Perform detection by running the model with the image as input 130 | interpreter.set_tensor(input_details[0]['index'],input_data) 131 | interpreter.invoke() 132 | 133 | # Retrieve detection results 134 | boxes = interpreter.get_tensor(output_details[boxes_idx]['index'])[0] # Bounding box coordinates of detected objects 135 | classes = interpreter.get_tensor(output_details[classes_idx]['index'])[0] # Class index of detected objects 136 | scores = interpreter.get_tensor(output_details[scores_idx]['index'])[0] # Confidence of detected objects 137 | 138 | # Loop over all detections and process each detection if its confidence is above minimum threshold 139 | for i in range(len(scores)): 140 | if ((scores[i] > min_conf_threshold) and (scores[i] <= 1.0)): 141 | 142 | # Get bounding box coordinates 143 | # Interpreter can return coordinates that are outside of image dimensions, need to force them to be within image using max() and min() 144 | ymin = int(max(1,(boxes[i][0] * imH))) 145 | xmin = int(max(1,(boxes[i][1] * imW))) 146 | ymax = int(min(imH,(boxes[i][2] * imH))) 147 | xmax = int(min(imW,(boxes[i][3] * imW))) 148 | 149 | # Draw bounding box 150 | cv2.rectangle(frame, (xmin,ymin), (xmax,ymax), (10, 255, 0), 2) 151 | 152 | # Get object's name and draw label 153 | object_name = labels[int(classes[i])] # Look up object name from "labels" array using class index 154 | label = '%s: %d%%' % (object_name, int(scores[i]*100)) # Example: 'quarter: 72%' 155 | labelSize, baseLine = cv2.getTextSize(label, cv2.FONT_HERSHEY_SIMPLEX, 0.7, 2) # Get font size 156 | label_ymin = max(ymin, labelSize[1] + 10) # Make sure not to draw label too close to top of window 157 | cv2.rectangle(frame, (xmin, label_ymin-labelSize[1]-10), (xmin+labelSize[0], label_ymin+baseLine-10), (255, 255, 255), cv2.FILLED) # Draw white box to put label text in 158 | cv2.putText(frame, label, (xmin, label_ymin-7), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 0), 2) # Draw label text 159 | 160 | # Assign the value of this coin based on the class name of the detected object 161 | # (There are more efficient ways to do this, but this shows an example of how to trigger an action when a certain class is detected) 162 | if object_name == 'penny': 163 | this_coin_value = 0.01 164 | elif object_name == 'nickel': 165 | this_coin_value = 0.05 166 | elif object_name == 'dime': 167 | this_coin_value = 0.10 168 | elif object_name == 'quarter': 169 | this_coin_value = 0.25 170 | 171 | # Add this coin's value to the running total 172 | total_coin_value = total_coin_value + this_coin_value 173 | 174 | 175 | # Now that we've gone through every detection, we know the total value of all coins in the frame. Let's display it in the corner of the frame. 176 | cv2.putText(frame,'Total change:',(20,80),cv2.FONT_HERSHEY_PLAIN,2,(0,0,0),4,cv2.LINE_AA) 177 | cv2.putText(frame,'Total change:',(20,80),cv2.FONT_HERSHEY_PLAIN,2,(230,230,230),2,cv2.LINE_AA) 178 | cv2.putText(frame,'$%.2f' % total_coin_value,(260,85),cv2.FONT_HERSHEY_PLAIN,2.5,(0,0,0),4,cv2.LINE_AA) 179 | cv2.putText(frame,'$%.2f' % total_coin_value,(260,85),cv2.FONT_HERSHEY_PLAIN,2.5,(85,195,105),2,cv2.LINE_AA) 180 | 181 | # Draw framerate in corner of frame 182 | cv2.putText(frame,'FPS: %.2f' % frame_rate_calc,(20,50),cv2.FONT_HERSHEY_PLAIN,2,(0,0,0),4,cv2.LINE_AA) 183 | cv2.putText(frame,'FPS: %.2f' % frame_rate_calc,(20,50),cv2.FONT_HERSHEY_PLAIN,2,(230,230,230),2,cv2.LINE_AA) 184 | 185 | # All the results have been drawn on the frame, so it's time to display it. 186 | cv2.imshow('Object detector', frame) 187 | 188 | # Calculate framerate 189 | t2 = cv2.getTickCount() 190 | time1 = (t2-t1)/freq 191 | frame_rate_calc= 1/time1 192 | 193 | # Press 'q' to quit 194 | if cv2.waitKey(1) == ord('q'): 195 | break 196 | 197 | # Clean up 198 | cv2.destroyAllWindows() 199 | cap.release() 200 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # TensorFlow Lite Object Detection Examples 2 | You've trained a TensorFlow Lite object detection model, but now how do you build an actual program around it? This folder provides code for using TensorFlow Lite object detection models in example applications. 3 | 4 | ### ChangeCounter.py 5 | -------------------------------------------------------------------------------- /get_pi_requirements.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Get packages required for OpenCV 4 | 5 | sudo apt-get -y install libjpeg-dev libtiff5-dev libjasper-dev libpng12-dev 6 | sudo apt-get -y install libavcodec-dev libavformat-dev libswscale-dev libv4l-dev 7 | sudo apt-get -y install libxvidcore-dev libx264-dev 8 | sudo apt-get -y install qt4-dev-tools 9 | sudo apt-get -y install libatlas-base-dev 10 | 11 | # Need to get an older version of OpenCV because version 4 has errors 12 | pip3 install opencv-python==3.4.11.41 13 | 14 | # Get packages required for TensorFlow 15 | # Using the tflite_runtime packages available at https://www.tensorflow.org/lite/guide/python 16 | # Will change to just 'pip3 install tensorflow' once newer versions of TF are added to piwheels 17 | 18 | #pip3 install tensorflow 19 | 20 | version=$(python3 -c 'import sys; print(".".join(map(str, sys.version_info[:2])))') 21 | 22 | if [ $version == "3.9" ]; then 23 | pip3 install https://github.com/google-coral/pycoral/releases/download/v2.0.0/tflite_runtime-2.5.0.post1-cp39-cp39-linux_armv7l.whl 24 | fi 25 | 26 | if [ $version == "3.8" ]; then 27 | pip3 install https://github.com/google-coral/pycoral/releases/download/v2.0.0/tflite_runtime-2.5.0.post1-cp38-cp38-linux_armv7l.whl 28 | fi 29 | 30 | if [ $version == "3.7" ]; then 31 | pip3 install https://github.com/google-coral/pycoral/releases/download/v2.0.0/tflite_runtime-2.5.0.post1-cp37-cp37m-linux_armv7l.whl 32 | fi 33 | 34 | if [ $version == "3.6" ]; then 35 | pip3 install https://github.com/google-coral/pycoral/releases/download/v2.0.0/tflite_runtime-2.5.0.post1-cp36-cp36m-linux_armv7l.whl 36 | fi 37 | 38 | if [ $version == "3.5" ]; then 39 | pip3 install https://github.com/google-coral/pycoral/releases/download/release-frogfish/tflite_runtime-2.5.0-cp35-cp35m-linux_armv7l.whl 40 | fi 41 | -------------------------------------------------------------------------------- /test.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EdjeElectronics/TensorFlow-Lite-Object-Detection-on-Android-and-Raspberry-Pi/f7f1fdec0f3e48161d598f99bf4657adbea944e8/test.mp4 -------------------------------------------------------------------------------- /test1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EdjeElectronics/TensorFlow-Lite-Object-Detection-on-Android-and-Raspberry-Pi/f7f1fdec0f3e48161d598f99bf4657adbea944e8/test1.jpg -------------------------------------------------------------------------------- /util_scripts/README.md: -------------------------------------------------------------------------------- 1 | ## Utility Scripts for TensorFlow Lite Object Detection 2 | These scripts are used in the [TFLite Training Colab](https://colab.research.google.com/github/EdjeElectronics/TensorFlow-Lite-Object-Detection-on-Android-and-Raspberry-Pi/blob/master/Train_TFLite2_Object_Detction_Model.ipynb) to help with various steps of training a custom model. They can also be used as standalone tools. 3 | 4 | ### Calculate model mAP - (calculate_map_cartucho.py) 5 | Calculate your TFLite detection model's mAP score! I'll share instructions on how to use this outside the Colab notebook later. 6 | 7 | 8 | 9 | This tool uses the main.py script from [Cartucho's excellent repository](https://github.com/Cartucho/mAP), which takes in ground truth data and detection results to calculate average precision at a certain IoU threshold. The calculate_map_cartucho.py script performs the mAP calculation at multiple IoU thresholds to determine the COCO metric for average mAP @ 0.5:0.95. 10 | 11 | ### Split images into train, test, and validation sets - (train_val_test.py) 12 | This script takes a folder full of images and randomly splits them between train, test, and validation folders. It does an 80%/10%/10% split by default, but this can be modified by changing the `train_percent`, `test_percent`, and `val_percent` variables in the code. 13 | 14 | ### Create CSV annotation file - (create_csv.py) 15 | This script creates a single CSV data file from a set of Pascal VOC annotation files. 16 | 17 | Original credit for the script goes to [datitran](https://github.com/datitran/raccoon_dataset/blob/master/xml_to_csv.py). 18 | 19 | ### Create TFRecord file - (create_tfrecord.py) 20 | This script creates TFRecord files from a CSV annotation data file and a folder of images. TFRecords are the [data format required](https://github.com/tensorflow/models/blob/master/research/object_detection/g3doc/preparing_inputs.md) by the TensorFlow Object Detection API for training. 21 | 22 | Original credit for the script goes to [datitran](https://github.com/datitran/raccoon_dataset/blob/master/generate_tfrecord.py). 23 | -------------------------------------------------------------------------------- /util_scripts/calculate_map_cartucho.py: -------------------------------------------------------------------------------- 1 | ##### Calculate object detection mAP score ##### 2 | # 3 | # Author: Evan Juras, EJ Technology Consultants (https://ejtech.io) 4 | # Date: 11/12/22 5 | # 6 | # Description: 7 | # This script determines an object detection model's mAP score using a calculator from https://github.com/Cartucho/mAP . 8 | # It calculates the COCO metric (mAP @ 0.5:0.95) by running the calculator script ("main.py") multiple times at different 9 | # IoU thresholds, then averaging the result from each run. It also supports Pascal VOC metric (mAP @ 0.5) and custom user metrics. 10 | 11 | import os 12 | import sys 13 | import argparse 14 | import numpy as np 15 | 16 | # Define and parse input arguments 17 | parser = argparse.ArgumentParser() 18 | parser.add_argument('--labels', help='Path to the labelmap file', default='labelmap.txt') 19 | parser.add_argument('--outdir', help='Output folder to save results in', default='outputs') 20 | parser.add_argument('--metric', help='mAP metric to calculate: "coco", "pascalvoc", or "custom"', default='coco') 21 | parser.add_argument('--iou', help='(Only if using --metric=custom) Specify IoU threshholds \ 22 | to use for evaluation (example: 0.5,0.6,0.7)') 23 | parser.add_argument('--show_images', help='Display and save images as they are evaluated', action='store_true') # Coming soon! 24 | parser.add_argument('--show_plots', help='Display and save plots showing precision/recall curve, mAP score, etc', action='store_true') # Coming soon! 25 | 26 | args = parser.parse_args() 27 | 28 | labelmap = args.labels 29 | outputs_dir = args.outdir 30 | metric = args.metric 31 | show_imgs = args.show_images 32 | show_plots = args.show_plots 33 | 34 | # Define which metric to use (i.e. which set of IoU thresholds to calculate mAP for) 35 | if metric=='coco': 36 | iou_threshes = [0.5,0.55,0.6,0.65,0.7,0.75,0.8,0.85,0.9,0.95] 37 | elif metric=='pascalvoc': 38 | iou_threshes = [0.5] 39 | elif metric=='custom': 40 | custom_ious = args.iou 41 | try: 42 | iou_threshes = [float(iou) for iou in custom_ious] 43 | except: 44 | print('Invalid entry for --iou. Example of a correct entry: "--iou=0.5,0.6,0.7"') 45 | sys.exit() 46 | else: 47 | print('Invalid entry for --metric. Please use coco, pascalvoc, or custom.') 48 | sys.exit() 49 | 50 | # Get file paths 51 | cwd = os.getcwd() 52 | output_path = os.path.join(cwd,outputs_dir) 53 | labelmap_path = os.path.join(cwd,labelmap) 54 | 55 | # Define arguments to show images and plots (if desired by user) 56 | if show_imgs: show_img_arg = '' 57 | else: show_img_arg = ' -na' # "-na" argument tells main.py NOT to show images 58 | 59 | if show_plots: show_plot_arg = '' 60 | else: show_plot_arg = ' -np' # "-np" argument tells main.py NOT to show plots 61 | 62 | 63 | # Load the label map 64 | with open(labelmap_path, 'r') as f: 65 | classes = [line.strip() for line in f.readlines()] 66 | 67 | # Make folder to store output result files 68 | if os.path.exists(output_path): 69 | print('The output folder %s already exists. Please delete it or specify a different folder name using --outdir.' % output_path) 70 | sys.exit() 71 | else: 72 | os.makedirs(output_path) 73 | 74 | # Create dictionary to store overall mAP results and results for each class 75 | mAP_results = {'overall':np.zeros(len(iou_threshes))} 76 | for classname in classes: 77 | mAP_results[classname] = np.zeros(len(iou_threshes)) # Add each class to dict 78 | 79 | for i, iou_thresh in enumerate(iou_threshes): 80 | 81 | # Modify main.py to use the specified IoU value 82 | with open('main.py', 'r') as f: 83 | data = f.read() 84 | 85 | # Set IoU threshold value 86 | data = data.replace('MINOVERLAP = 0.5', 'MINOVERLAP = %.2f' % iou_thresh) 87 | f.close() 88 | 89 | with open('main_modified.py', 'w') as f: 90 | f.write(data) 91 | 92 | # Run modified script 93 | print('Calculating mAP at %.2f IoU threshold...' % iou_thresh) 94 | os.system('python main_modified.py' + show_img_arg + show_plot_arg) 95 | 96 | # Extract mAP values by manually parsing the output.txt file 97 | with open('output/output.txt', 'r',) as f: 98 | for line in f: 99 | if '%' in line: 100 | # Overall mAP result is stored as "mAP = score%" (example: "mAP = 63.52%") 101 | if 'mAP' in line: 102 | vals = line.split(' ') 103 | overall_mAP = float(vals[2].replace('%','')) 104 | mAP_results['overall'][i] = overall_mAP 105 | # Class mAP results are stored as "score% = class AP" (example: "78.30% = dime AP") 106 | else: 107 | vals = line.split(' ') 108 | class_name = vals[2] 109 | class_mAP = float(vals[0].replace('%','')) 110 | mAP_results[class_name][i] = class_mAP 111 | 112 | # Save mAP results for this IoU value as a different folder name, then delete modified script 113 | newpath = os.path.join(output_path,'output_iou_%.2f' % iou_thresh) 114 | os.rename('output',newpath) 115 | os.remove('main_modified.py') 116 | 117 | # Okay, we found mAP at each IoU value! Now we just need to average the mAPs and display them. 118 | class_mAP_result = [] 119 | print('\n***mAP Results***\n') 120 | print('Class\t\tAverage mAP @ 0.5:0.95') 121 | print('---------------------------------------') 122 | for classname in classes: 123 | class_vals = mAP_results[classname] 124 | class_avg = np.mean(class_vals) 125 | class_mAP_result.append(class_avg) 126 | print('%s\t\t%0.2f%%' % (classname, class_avg)) # TO DO: Find a better variable name than "classname" 127 | 128 | overall_mAP_result = np.mean(class_mAP_result) 129 | print('\nOverall\t\t%0.2f%%' % overall_mAP_result) 130 | 131 | -------------------------------------------------------------------------------- /util_scripts/create_csv.py: -------------------------------------------------------------------------------- 1 | # Script to create CSV data file from Pascal VOC annotation files 2 | # Based off code from GitHub user datitran: https://github.com/datitran/raccoon_dataset/blob/master/xml_to_csv.py 3 | 4 | import os 5 | import glob 6 | import pandas as pd 7 | import xml.etree.ElementTree as ET 8 | 9 | def xml_to_csv(path): 10 | xml_list = [] 11 | for xml_file in glob.glob(path + '/*.xml'): 12 | tree = ET.parse(xml_file) 13 | root = tree.getroot() 14 | for member in root.findall('object'): 15 | value = (root.find('filename').text, 16 | int(root.find('size')[0].text), 17 | int(root.find('size')[1].text), 18 | member[0].text, 19 | int(member[4][0].text), 20 | int(member[4][1].text), 21 | int(member[4][2].text), 22 | int(member[4][3].text) 23 | ) 24 | xml_list.append(value) 25 | column_name = ['filename', 'width', 'height', 'class', 'xmin', 'ymin', 'xmax', 'ymax'] 26 | xml_df = pd.DataFrame(xml_list, columns=column_name) 27 | return xml_df 28 | 29 | def main(): 30 | for folder in ['train','validation']: 31 | image_path = os.path.join(os.getcwd(), ('images/' + folder)) 32 | xml_df = xml_to_csv(image_path) 33 | xml_df.to_csv(('images/' + folder + '_labels.csv'), index=None) 34 | print('Successfully converted xml to csv.') 35 | 36 | main() 37 | -------------------------------------------------------------------------------- /util_scripts/create_tfrecord.py: -------------------------------------------------------------------------------- 1 | # Script to create TFRecord files from train and test dataset folders 2 | # Originally from GitHub user datitran: https://github.com/datitran/raccoon_dataset/blob/master/generate_tfrecord.py 3 | 4 | """ 5 | Usage: 6 | # From tensorflow/models/ 7 | # Create train data: 8 | python generate_tfrecord.py --csv_input=images/train_labels.csv --image_dir=images/train --output_path=train.record 9 | 10 | # Create test data: 11 | python generate_tfrecord.py --csv_input=images/test_labels.csv --image_dir=images/test --output_path=test.record 12 | """ 13 | from __future__ import division 14 | from __future__ import print_function 15 | from __future__ import absolute_import 16 | 17 | import os 18 | import io 19 | import pandas as pd 20 | 21 | from tensorflow.python.framework.versions import VERSION 22 | if VERSION >= "2.0.0a0": 23 | import tensorflow.compat.v1 as tf 24 | else: 25 | import tensorflow as tf 26 | 27 | from PIL import Image 28 | from object_detection.utils import dataset_util 29 | from collections import namedtuple, OrderedDict 30 | 31 | flags = tf.app.flags 32 | flags.DEFINE_string('csv_input', '', 'Path to the CSV input') 33 | flags.DEFINE_string('labelmap', '', 'Path to the labelmap file') 34 | flags.DEFINE_string('image_dir', '', 'Path to the image directory') 35 | flags.DEFINE_string('output_path', '', 'Path to output TFRecord') 36 | FLAGS = flags.FLAGS 37 | 38 | def split(df, group): 39 | data = namedtuple('data', ['filename', 'object']) 40 | gb = df.groupby(group) 41 | return [data(filename, gb.get_group(x)) for filename, x in zip(gb.groups.keys(), gb.groups)] 42 | 43 | 44 | def create_tf_example(group, path): 45 | with tf.gfile.GFile(os.path.join(path, '{}'.format(group.filename)), 'rb') as fid: 46 | encoded_jpg = fid.read() 47 | encoded_jpg_io = io.BytesIO(encoded_jpg) 48 | image = Image.open(encoded_jpg_io) 49 | width, height = image.size 50 | 51 | filename = group.filename.encode('utf8') 52 | image_format = b'jpg' 53 | xmins = [] 54 | xmaxs = [] 55 | ymins = [] 56 | ymaxs = [] 57 | classes_text = [] 58 | classes = [] 59 | 60 | labels = [] 61 | with open(FLAGS.labelmap, 'r') as f: 62 | labels = [line.strip() for line in f.readlines()] 63 | 64 | for index, row in group.object.iterrows(): 65 | xmins.append(row['xmin'] / width) 66 | xmaxs.append(row['xmax'] / width) 67 | ymins.append(row['ymin'] / height) 68 | ymaxs.append(row['ymax'] / height) 69 | classes_text.append(row['class'].encode('utf8')) 70 | classes.append(int(labels.index(row['class'])+1)) 71 | 72 | tf_example = tf.train.Example(features=tf.train.Features(feature={ 73 | 'image/height': dataset_util.int64_feature(height), 74 | 'image/width': dataset_util.int64_feature(width), 75 | 'image/filename': dataset_util.bytes_feature(filename), 76 | 'image/source_id': dataset_util.bytes_feature(filename), 77 | 'image/encoded': dataset_util.bytes_feature(encoded_jpg), 78 | 'image/format': dataset_util.bytes_feature(image_format), 79 | 'image/object/bbox/xmin': dataset_util.float_list_feature(xmins), 80 | 'image/object/bbox/xmax': dataset_util.float_list_feature(xmaxs), 81 | 'image/object/bbox/ymin': dataset_util.float_list_feature(ymins), 82 | 'image/object/bbox/ymax': dataset_util.float_list_feature(ymaxs), 83 | 'image/object/class/text': dataset_util.bytes_list_feature(classes_text), 84 | 'image/object/class/label': dataset_util.int64_list_feature(classes), 85 | })) 86 | return tf_example 87 | 88 | 89 | def main(_): 90 | # Load and prepare data 91 | writer = tf.python_io.TFRecordWriter(FLAGS.output_path) 92 | path = os.path.join(os.getcwd(), FLAGS.image_dir) 93 | examples = pd.read_csv(FLAGS.csv_input) 94 | 95 | # Create TFRecord files 96 | grouped = split(examples, 'filename') 97 | for group in grouped: 98 | tf_example = create_tf_example(group, path) 99 | writer.write(tf_example.SerializeToString()) 100 | 101 | writer.close() 102 | output_path = os.path.join(os.getcwd(), FLAGS.output_path) 103 | print('Successfully created the TFRecords: {}'.format(output_path)) 104 | 105 | # Create labelmap.pbtxt file 106 | path_to_labeltxt = os.path.join(os.getcwd(), FLAGS.labelmap) 107 | with open(path_to_labeltxt, 'r') as f: 108 | labels = [line.strip() for line in f.readlines()] 109 | 110 | path_to_labelpbtxt = os.path.join(os.getcwd(), 'labelmap.pbtxt') 111 | with open(path_to_labelpbtxt,'w') as f: 112 | for i, label in enumerate(labels): 113 | f.write('item {\n' + 114 | ' id: %d\n' % (i + 1) + 115 | ' name: \'%s\'\n' % label + 116 | '}\n' + 117 | '\n') 118 | 119 | if __name__ == '__main__': 120 | tf.app.run() 121 | -------------------------------------------------------------------------------- /util_scripts/train_val_test_split.py: -------------------------------------------------------------------------------- 1 | ### Python script to split a labeled image dataset into Train, Validation, and Test folders. 2 | # Author: Evan Juras, EJ Technology Consultants 3 | # Date: 4/10/21 4 | 5 | # Randomly splits images to 80% train, 10% validation, and 10% test, and moves them to their respective folders. 6 | # This script is intended to be used in the TFLite Object Detection Colab notebook here: 7 | # https://colab.research.google.com/github/EdjeElectronics/TensorFlow-Lite-Object-Detection-on-Android-and-Raspberry-Pi/blob/master/Train_TFLite2_Object_Detction_Model.ipynb 8 | 9 | from pathlib import Path 10 | import random 11 | import os 12 | import sys 13 | 14 | # Define paths to image folders 15 | image_path = '/content/images/all' 16 | train_path = '/content/images/train' 17 | val_path = '/content/images/validation' 18 | test_path = '/content/images/test' 19 | 20 | # Get list of all images 21 | jpeg_file_list = [path for path in Path(image_path).rglob('*.jpeg')] 22 | jpg_file_list = [path for path in Path(image_path).rglob('*.jpg')] 23 | png_file_list = [path for path in Path(image_path).rglob('*.png')] 24 | bmp_file_list = [path for path in Path(image_path).rglob('*.bmp')] 25 | 26 | if sys.platform == 'linux': 27 | JPEG_file_list = [path for path in Path(image_path).rglob('*.JPEG')] 28 | JPG_file_list = [path for path in Path(image_path).rglob('*.JPG')] 29 | file_list = jpg_file_list + JPG_file_list + png_file_list + bmp_file_list + JPEG_file_list + jpeg_file_list 30 | else: 31 | file_list = jpg_file_list + png_file_list + bmp_file_list + jpeg_file_list 32 | 33 | file_num = len(file_list) 34 | print('Total images: %d' % file_num) 35 | 36 | # Determine number of files to move to each folder 37 | train_percent = 0.8 # 80% of the files go to train 38 | val_percent = 0.1 # 10% go to validation 39 | test_percent = 0.1 # 10% go to test 40 | train_num = int(file_num*train_percent) 41 | val_num = int(file_num*val_percent) 42 | test_num = file_num - train_num - val_num 43 | print('Images moving to train: %d' % train_num) 44 | print('Images moving to validation: %d' % val_num) 45 | print('Images moving to test: %d' % test_num) 46 | 47 | # Select 80% of files randomly and move them to train folder 48 | for i in range(train_num): 49 | move_me = random.choice(file_list) 50 | fn = move_me.name 51 | base_fn = move_me.stem 52 | parent_path = move_me.parent 53 | xml_fn = base_fn + '.xml' 54 | os.rename(move_me, train_path+'/'+fn) 55 | os.rename(os.path.join(parent_path,xml_fn),os.path.join(train_path,xml_fn)) 56 | file_list.remove(move_me) 57 | 58 | # Select 10% of remaining files and move them to validation folder 59 | for i in range(val_num): 60 | move_me = random.choice(file_list) 61 | fn = move_me.name 62 | base_fn = move_me.stem 63 | parent_path = move_me.parent 64 | xml_fn = base_fn + '.xml' 65 | os.rename(move_me, val_path+'/'+fn) 66 | os.rename(os.path.join(parent_path,xml_fn),os.path.join(val_path,xml_fn)) 67 | file_list.remove(move_me) 68 | 69 | # Move remaining files to test folder 70 | for i in range(test_num): 71 | move_me = random.choice(file_list) 72 | fn = move_me.name 73 | base_fn = move_me.stem 74 | parent_path = move_me.parent 75 | xml_fn = base_fn + '.xml' 76 | os.rename(move_me, test_path+'/'+fn) 77 | os.rename(os.path.join(parent_path,xml_fn),os.path.join(test_path,xml_fn)) 78 | file_list.remove(move_me) 79 | -------------------------------------------------------------------------------- /util_scripts/train_val_test_split_yolo.py: -------------------------------------------------------------------------------- 1 | ### Python script to split a labeled image dataset into Train, Validation, and Test folders. Modified to work for YOLO txt files. 2 | # Author: Evan Juras, EJ Technology Consultants 3 | # Date: 4/10/21 4 | 5 | # Randomly splits images to 80% train, 10% validation, and 10% test, and moves them to their respective folders. 6 | # This script is intended to be used in the TFLite Object Detection Colab notebook here: 7 | # https://colab.research.google.com/github/EdjeElectronics/TensorFlow-Lite-Object-Detection-on-Android-and-Raspberry-Pi/blob/master/Train_TFLite2_Object_Detction_Model.ipynb 8 | 9 | from pathlib import Path 10 | import random 11 | import os 12 | import sys 13 | 14 | # Define paths to image folders 15 | image_path = '/content/images/all' 16 | train_path = '/content/images/train' 17 | val_path = '/content/images/validation' 18 | test_path = '/content/images/test' 19 | 20 | # Get list of all images 21 | jpeg_file_list = [path for path in Path(image_path).rglob('*.jpeg')] 22 | jpg_file_list = [path for path in Path(image_path).rglob('*.jpg')] 23 | png_file_list = [path for path in Path(image_path).rglob('*.png')] 24 | bmp_file_list = [path for path in Path(image_path).rglob('*.bmp')] 25 | 26 | if sys.platform == 'linux': 27 | JPEG_file_list = [path for path in Path(image_path).rglob('*.JPEG')] 28 | JPG_file_list = [path for path in Path(image_path).rglob('*.JPG')] 29 | file_list = jpg_file_list + JPG_file_list + png_file_list + bmp_file_list + JPEG_file_list + jpeg_file_list 30 | else: 31 | file_list = jpg_file_list + png_file_list + bmp_file_list + jpeg_file_list 32 | 33 | file_num = len(file_list) 34 | print('Total images: %d' % file_num) 35 | 36 | # Determine number of files to move to each folder 37 | train_percent = 0.8 # 80% of the files go to train 38 | val_percent = 0.1 # 10% go to validation 39 | test_percent = 0.1 # 10% go to test 40 | train_num = int(file_num*train_percent) 41 | val_num = int(file_num*val_percent) 42 | test_num = file_num - train_num - val_num 43 | print('Images moving to train: %d' % train_num) 44 | print('Images moving to validation: %d' % val_num) 45 | print('Images moving to test: %d' % test_num) 46 | 47 | # Select 80% of files randomly and move them to train folder 48 | for i in range(train_num): 49 | move_me = random.choice(file_list) 50 | fn = move_me.name 51 | base_fn = move_me.stem 52 | parent_path = move_me.parent 53 | txt_fn = base_fn + '.txt' 54 | os.rename(move_me, train_path+'/'+fn) 55 | os.rename(os.path.join(parent_path,txt_fn),os.path.join(train_path,txt_fn)) 56 | file_list.remove(move_me) 57 | 58 | # Select 10% of remaining files and move them to validation folder 59 | for i in range(val_num): 60 | move_me = random.choice(file_list) 61 | fn = move_me.name 62 | base_fn = move_me.stem 63 | parent_path = move_me.parent 64 | txt_fn = base_fn + '.txt' 65 | os.rename(move_me, val_path+'/'+fn) 66 | os.rename(os.path.join(parent_path,txt_fn),os.path.join(val_path,txt_fn)) 67 | file_list.remove(move_me) 68 | 69 | # Move remaining files to test folder 70 | for i in range(test_num): 71 | move_me = random.choice(file_list) 72 | fn = move_me.name 73 | base_fn = move_me.stem 74 | parent_path = move_me.parent 75 | txt_fn = base_fn + '.txt' 76 | os.rename(move_me, test_path+'/'+fn) 77 | os.rename(os.path.join(parent_path,txt_fn),os.path.join(test_path,txt_fn)) 78 | file_list.remove(move_me) 79 | --------------------------------------------------------------------------------