Nina Diana

Nina Diana

1564046853

Building a Gas Pump Scanner with OpenCV/Python/iOS

Last year I took the Coursera course on Machine Learning and one of my goals in taking that class was that I wanted to use something learned from that in one of my personal projects. Back in 2012, in an exercise in learning iOS development, I released a Gas Tracking App named FuelMate in the iOS app store. You can use the app to track your gas mileage over time and has some fun features like an Apple Watch app, vin.li integration, and visuals based on your trending mpg.

I had a new idea for it, how about adding a feature that instead of typing in the fuel information into the app, you could just scan it directly from the pump? Let’s dive into how we can make that happen.

Technology

Rather than starting with updating the iOS application, I wanted to prove it out using something easier/faster. I decided that my first goal of this project was to write a simple Python app to take an image of a gas pump, and try to read the digits from it. Once I’ve proved out the idea, I’d then work on integrating that into iOS.

OpenCV is a popular cross platform library used for Computer Vision applications. It includes a wide variety of image manipulation utilities as well as some Machine Learning functionality. Since it’s cross platform, my hope was that I could prototype this in Python first, and then convert the processing code into C++ to run on the iOS app.

My Python skills aren’t very advanced, so this was also a learning exercise in Python. I’ve released all the Python code I’ve written for this project on GitHub. This article isn’t a technical code review of the code, but rather a high level writeup on the thought process going into this project.

Goals

Before touching anything in the iOS code, I had two main ideas to prove:

  • Can I isolate the digits from the image?
  • Can I determine which digit the image represents?

Once I’ve validated these two, I’d then invest the time in updating the iOS app. Historically anytime I touch my iOS apps, it turns into a much larger time investment than I usually expect.

Digit Isolation

There are multiple ways in how you can determine the digits in the image, but I set forth using simple image thresholding to attempt to find the digits.

The basic idea of image thresholding is that you convert the image to grayscale, then say that any pixel where the grayscale value is less than some constant, then that pixel is one value, otherwise it’s another. In the end you have a binary image that is just two colors, which in most cases is just black and white.

This concept is very effective in OCR applications, but the main problem is deciding on what to use for that threshold value. You can either pick some constant, or with OpenCV, you have a few additional options. Rather than use the constant, you can use adaptive thresholding, this will use smaller portions of the image and determine different thresholds to use for you. This is extremely useful in applications with different lighting situations, especially in scanning gas pumps.

Once you have the image thresholded, you can use OpenCV’s findContours method to find areas of the image that have connected sections of white pixels. Once you have the contours, then you can crop out those areas and determine if they might be a digit and what digit it is.

Basic Image Processing Flow

Here is the original image I used in testing the image processing. It has a few glare spots, but is a fairly clean image. Let’s step through the process of taking this source image and try to break it down to the individual digits.

Image Prep

Before starting the image manipulation flow, I decided to adjust some of the image properties before continuing. This was a bit of trial and error, but I noticed that when I adjusted the exposure of the image, I was able to get better results. Below is the image after I adjusted the exposure(alpha) using the Python equivalent of cv::Mat::convertTo which is just a multiply action on the image mat cv2.multiply(some_img, np.array([some_alpha]) You can see the code here

Grayscale

Convert the image to grayscale.

Blur

Blur the image to reduce the noise. I tried a bunch of different blur options, but found the best results with just a mild blur.

Threshold the image to convert to black and white

In the image below, I’m using the cv2.adaptiveThresholdmethod with the cv2.ADAPTIVE_THRES_GAUSSIAN_C option. This method takes in two parameters, block size and a constant to adjust by. Determining these two took some trial and error, more on that below in the optimization section.

Fill the gaps

Since most fuel pumps use some kind 7 segment lcd display, there are slight gaps in the digits that won’t work with the contouring method, so we need to make those segments seem connected. In this case, we’re going to erode the image to connect those gaps. That may seem backwards, since you might expect to use dilate , but those methods typically work on the white sections of the image. In our case we’re ‘eroding’ away the white background to make the digits seem bigger.

Inverse the Image

Before we try to find the contours in the image, we need to reverse the colors because the findContours method will find connected sections of white and currently the digits are black.

Find the contours on the image

The image below shows our original image with bounding boxes around each of the contours it found on the image above. You can see that it found the digits, but also a bunch of things that are not digits, so we’ll need to filter those out.

Contour Filtering

Now that we have a bunch of contours, we need to figure out which contours we care about. After looking through a bunch of gas pump displays and scenarios, I came up with a quick set of rules it’ll apply to the contours.

  1. Collect any square contours that we’ll classify as a potential decimal.
  2. Throw out anything that’s not a square or a tall rectangle.
  3. Match contours with certain aspect ratios. Nine of the ten digits in an LCD display would have an aspect ratio similar to one of the blue box highlights below. The exception to that rule is the digit ‘1’, which has a slightly different aspect ratio. By using some sample contours, I determined the 0–9!1 aspect as 0.6 and the 1 aspect as 0.3 . It’ll use those ratios with a +/- buffer to determine if the contour is something we want, and collect those contours.
  4. Apply an additional set of rules to the potential digits where we’ll determine if the contour bounds deviates from the average height or vertical position of all the other potential digits. Since the digits should be the same size and lined up on the same Y, we can throw away any contours that it think is a digit, but not lined up and sized like the others.

Predict

With two buckets of contours, one with potential digits and one with potential decimals, we can crop the image with those contour bounds and feed those into a trained system to predict it’s value. More on that process below in the ‘Digit Training’ section.

Find the Decimal

Finding the decimal in the image is a bit of a different problem to solve. Since it’s small and sometimes gets connected to the digit next to it, it seemed problematic to determine it using the method we used on the digits. When we filtered the contours, we collected the square contours that could be a decimal. Once we have our validated digit contours from the previous step, we’ll find the leftmost x position of the digits and the rightmost x position, to determine the space we’d expect a decimal. Then we’ll loop over those potential decimals, determine if it’s in that space and in the lower half of that space and classify that as the decimal. With the decimal found, we can then insert that into the string of numbers we’ve predicted above.

Digit Training

In the world of Machine Learning, solving an OCR problem is a classification problem. We build up a set of trained data, for example the digits from the image processing, classify them as something, and then use that data to match any new images. Once I got the basic image isolation working, I created a script that could loop over a folder of images, run the digit isolation code, and then save the cropped digits into a new folder for me to review. After that ran, I’d have a folder of untrained digits that I could then use to train a system.

Since OpenCV already included a k-nearest neighbor (k-NN) implementation, I figured I’d give that a try at first, so I wouldn’t have to bring in any additional libraries. In order to train that, I went through the folder of digit crops and then dropped them into new folders labeled 0–9, so each folder had a collection of different versions of the number in it. I didn’t have a ton of these images, but had enough to prove this would work. Since these digits are fairly standard, I don’t think I’d need a ton of trained images to be fairly accurate.

The basics of how k-NN works is that we’ll load up each image in black and white, store that image in an array where each pixel is either on or off, and then associate those on/off pixels with a specific digit. Then when we have a new image to predict, it’ll figure out of which of the trained images has the closest match of these pixels, and return us the value that matches the closest to.

Once I had my digits organized, I created a new script that would traverse those folders, take each image and associate that image with a digit. In most of the code so far, the general image processing concepts are applied the same in both Python and C++, but here is where I came across a slight difference.

In most Python examples of this type of application, the classifications are written to two files, one containing the classification, and the other containing the image contents for that classification. This usually done with NumPy and standard text files. However, since I wanted to reuse this system on my iOS application, I needed to come up with a way that I could have cross-platform classification files. At the time, I couldn’t find anything to do this, so ended up writing a quick utility that will take the classification data from Python and serialize that into a JSON file that I could use on the C++ side with OpenCV’s FileStorage system. It’s not pretty, but I wrote a simple Mat serialization method in Python that’ll create the proper structure for OpenCV to read on the iOS side. Now when I train the digits, I’ll get the NumPy files for use in my Python testing, and then a JSON doc I can drag into my iOS application. You can see that code here.

Optimization

Once I validated my two goals of digit isolation and prediction, it was a matter of optimizing the algorithm to predict digits on new images of pumps.

Manual

During the initial phases of optimization, I created a simple playground application that uses some of the simple UI components that OpenCV provides you. With these components, you can create some simple trackbars to slide around and change different values and reprocess the image. I created a little wrapper around the cv2.imshow method that would tile the shown windows because I got sick of always repositioning them, you can see that here.

Using the playground, I was able to load up different images and try out different variations of the variables in the image processing and determined what I thought was the best combination. This got me close, but I wanted to validate these parameters more systematically.

Automated

Testing the different variables on each image was a good way to get started, but I wanted a better way to validate that if I changed the variables for one image, if that would have an affect on any of the other images. To do this, I came up with system to do some automated tests against these images.

I took each of my test images and put them in a folder. Then I named each file with the digits I expected in the image, using an ‘A’ for the decimal point. Then the application could load up each of the images in that directory and predict the digits and then compare that against the digits from the filename to determine if it was a match. This allowed me to try out my changes quickly against all the different images.

Taking this a step further, I created a different version of this script that would try out almost every combination of variables for blur, thresholding, etc on this set of images and figure out what the most optimized set of variables would be that had the best accuracy. This script took quite a bit longer to run, around 7 hours on my machine, but in the end came up with a different set of variables that I didn’t find when I tested it manually.

iOS Conversion

With the process and code working in Python, it was time to convert this logic over to C++ (technically Objective C++) so I could embed this logic in my iOS application. Thankfully most of the code is really easy to port from Python to C++ since a lot of the methods and signatures are all the same. One of the main differences being that in C++ the methods operating on an Image Mat work on a pointer to the image where the Python code returns an image instead.

I won’t go into all the details of how to connect the iPhone camera to the image processing, but the basic idea is that you can use the AVFoundation classes to setup an AVCaptureSession that will take input from the phone camera and stream the pixels to a buffer delegate where you can run your processing logic. This processing typically happens on a dispatch queue to avoid blocking your main thread.

In my app’s code, I take the raw pixel data and pass that into the processor that will end up returning a predicted digit string, an optional raw image after processing for debugging, and a basic confidence level of it’s prediction. Once it’s fairly confident with a value, it’ll display that on the screen.

One note on the usability in the implementation is that in order capture the digits, the user has to tap a button on the screen. I noticed in testing that the movement of your hand to hit the button sometimes would cause the digits to recalculate and you might end up with digits you didn’t want. So rather than take the digits from the point when you press the button, the code will take a running average of the most calculated value over the duration and display that one. This helps with any sudden movements that occur when you tap the button to save and also avoids a constant jitter of readings while the processor is running.

Here is a quick clip of the app running:

Conclusion

Whether or not this is a feature that anyone will actually ever use is yet to be determined, but it was a fun exercise in implementing some of the Machine Learning concepts and playing with OpenCV. Since I work from home, I don’t actually fill up my car very often, so I haven’t tested this a ton in the real world, but in the app you can send me your fuel pump images if the scanning doesn’t work and I can try to make it better in the future.

So far in my testing the biggest issue the app has is with the glare on the pump display. Depending on the lighting at the pump and the angle of the phone can lead to some scans that just don’t work. I’m still working on the best way to handle those issues.

For future releases, I’d love to be able to scan the entire pump display at once, and then find the digits and split them automatically into cost and amount. Due to the differences in pumps, it’s not always consistent where these numbers are and are sometimes in individual lcd panels.

I’m still messing with different ideas to isolate the digits, but figured I’d share what I have so far and see how it goes. Maybe there are much better ways to do this; if you know of any tips/tricks, let me know!

Further reading:

Python GUI Tutorial - Python GUI Programming Using Tkinter Tutorial

TensorFlow Variables And Placeholders Tutorial With Example

Top Python IDEs for Data Science in 2019

9 Tips to Trigger a Great Career in Machine Learning

Learning Model Building in Scikit-learn : A Python Machine Learning Library

#python

What is GEEK

Buddha Community

Building a Gas Pump Scanner with OpenCV/Python/iOS
Ray  Patel

Ray Patel

1619518440

top 30 Python Tips and Tricks for Beginners

Welcome to my Blog , In this article, you are going to learn the top 10 python tips and tricks.

1) swap two numbers.

2) Reversing a string in Python.

3) Create a single string from all the elements in list.

4) Chaining Of Comparison Operators.

5) Print The File Path Of Imported Modules.

6) Return Multiple Values From Functions.

7) Find The Most Frequent Value In A List.

8) Check The Memory Usage Of An Object.

#python #python hacks tricks #python learning tips #python programming tricks #python tips #python tips and tricks #python tips and tricks advanced #python tips and tricks for beginners #python tips tricks and techniques #python tutorial #tips and tricks in python #tips to learn python #top 30 python tips and tricks for beginners

Ray  Patel

Ray Patel

1619510796

Lambda, Map, Filter functions in python

Welcome to my Blog, In this article, we will learn python lambda function, Map function, and filter function.

Lambda function in python: Lambda is a one line anonymous function and lambda takes any number of arguments but can only have one expression and python lambda syntax is

Syntax: x = lambda arguments : expression

Now i will show you some python lambda function examples:

#python #anonymous function python #filter function in python #lambda #lambda python 3 #map python #python filter #python filter lambda #python lambda #python lambda examples #python map

Shardul Bhatt

Shardul Bhatt

1626775355

Why use Python for Software Development

No programming language is pretty much as diverse as Python. It enables building cutting edge applications effortlessly. Developers are as yet investigating the full capability of end-to-end Python development services in various areas. 

By areas, we mean FinTech, HealthTech, InsureTech, Cybersecurity, and that's just the beginning. These are New Economy areas, and Python has the ability to serve every one of them. The vast majority of them require massive computational abilities. Python's code is dynamic and powerful - equipped for taking care of the heavy traffic and substantial algorithmic capacities. 

Programming advancement is multidimensional today. Endeavor programming requires an intelligent application with AI and ML capacities. Shopper based applications require information examination to convey a superior client experience. Netflix, Trello, and Amazon are genuine instances of such applications. Python assists with building them effortlessly. 

5 Reasons to Utilize Python for Programming Web Apps 

Python can do such numerous things that developers can't discover enough reasons to admire it. Python application development isn't restricted to web and enterprise applications. It is exceptionally adaptable and superb for a wide range of uses.

Robust frameworks 

Python is known for its tools and frameworks. There's a structure for everything. Django is helpful for building web applications, venture applications, logical applications, and mathematical processing. Flask is another web improvement framework with no conditions. 

Web2Py, CherryPy, and Falcon offer incredible capabilities to customize Python development services. A large portion of them are open-source frameworks that allow quick turn of events. 

Simple to read and compose 

Python has an improved sentence structure - one that is like the English language. New engineers for Python can undoubtedly understand where they stand in the development process. The simplicity of composing allows quick application building. 

The motivation behind building Python, as said by its maker Guido Van Rossum, was to empower even beginner engineers to comprehend the programming language. The simple coding likewise permits developers to roll out speedy improvements without getting confused by pointless subtleties. 

Utilized by the best 

Alright - Python isn't simply one more programming language. It should have something, which is the reason the business giants use it. Furthermore, that too for different purposes. Developers at Google use Python to assemble framework organization systems, parallel information pusher, code audit, testing and QA, and substantially more. Netflix utilizes Python web development services for its recommendation algorithm and media player. 

Massive community support 

Python has a steadily developing community that offers enormous help. From amateurs to specialists, there's everybody. There are a lot of instructional exercises, documentation, and guides accessible for Python web development solutions. 

Today, numerous universities start with Python, adding to the quantity of individuals in the community. Frequently, Python designers team up on various tasks and help each other with algorithmic, utilitarian, and application critical thinking. 

Progressive applications 

Python is the greatest supporter of data science, Machine Learning, and Artificial Intelligence at any enterprise software development company. Its utilization cases in cutting edge applications are the most compelling motivation for its prosperity. Python is the second most well known tool after R for data analytics.

The simplicity of getting sorted out, overseeing, and visualizing information through unique libraries makes it ideal for data based applications. TensorFlow for neural networks and OpenCV for computer vision are two of Python's most well known use cases for Machine learning applications.

Summary

Thinking about the advances in programming and innovation, Python is a YES for an assorted scope of utilizations. Game development, web application development services, GUI advancement, ML and AI improvement, Enterprise and customer applications - every one of them uses Python to its full potential. 

The disadvantages of Python web improvement arrangements are regularly disregarded by developers and organizations because of the advantages it gives. They focus on quality over speed and performance over blunders. That is the reason it's a good idea to utilize Python for building the applications of the future.

#python development services #python development company #python app development #python development #python in web development #python software development

Verda  Conroy

Verda Conroy

1591743681

Create a Virtual Pen and Eraser with Python OpenCV - Genial Code

Learn Free how to create a virtual pen and eraser with python and OpenCV with source code and complete guide. This entire application is built fundamentally on contour detection. It can be thought of as something like closed color curves on compromises that have the same color or intensity, it’s like a blob. In this project we use color masking to get the binary mask of our target color pen, then we use the counter detection to find the location of this pen and the contour to find it.

#python #create virtual pen and eraser with opencv #create virtual pen and eraser with python opencv #programming #opencv #python opencv

Build an Android application with Kivy Python framework

If you’re a Python developer thinking about getting started with mobile development, then the Kivy framework is your best bet. With Kivy, you can develop platform-independent applications that compile for iOS, Android, Windows, macOS, and Linux. In this article, we’ll cover Android specifically because it is the most used.

We’ll build a simple random number generator app that you can install on your phone and test when you are done. To follow along with this article, you should be familiar with Python. Let’s get started!

Getting started with Kivy

First, you’ll need a new directory for your app. Make sure you have Python installed on your machine and open a new Python file. You’ll need to install the Kivy module from your terminal using either of the commands below. To avoid any package conflicts, be sure you’re installing Kivy in a virtual environment:

pip install kivy 
//
pip3 install kivy 

Once you have installed Kivy, you should see a success message from your terminal that looks like the screenshots below:

Kivy installation

Successful Kivy installation

 

Next, navigate into your project folder. In the main.py file, we’ll need to import the Kivy module and specify which version we want. You can use Kivy v2.0.0, but if you have a smartphone that is older than Android 8.0, I recommend using Kivy v1.9.0. You can mess around with the different versions during the build to see the differences in features and performance.

Add the version number right after the import kivy line as follows:

kivy.require('1.9.0')

Now, we’ll create a class that will basically define our app; I’ll name mine RandomNumber. This class will inherit the app class from Kivy. Therefore, you need to import the app by adding from kivy.app import App:

class RandomNumber(App): 

In the RandomNumber class, you’ll need to add a function called build, which takes a self parameter. To actually return the UI, we’ll use the build function. For now, I have it returned as a simple label. To do so, you’ll need to import Label using the line from kivy.uix.label import Label:

import kivy
from kivy.app import App
from kivy.uix.label import Label

class RandomNumber(App):
  def build(self):
    return Label(text="Random Number Generator")

Now, our app skeleton is complete! Before moving forward, you should create an instance of the RandomNumber class and run it in your terminal or IDE to see the interface:

import kivy from kivy.app import App from kivy.uix.label import Label class RandomNumber(App):  def build(self):    return Label(text="Random Number Generator") randomApp = RandomNumber() randomApp.run()

When you run the class instance with the text Random Number Generator, you should see a simple interface or window that looks like the screenshot below:

 

Simple interface after running the code

You won’t be able to run the text on Android until you’ve finished building the whole thing.

Outsourcing the interface

Next, we’ll need a way to outsource the interface. First, we’ll create a Kivy file in our directory that will house most of our design work. You’ll want to name this file the same name as your class using lowercase letters and a .kv extension. Kivy will automatically associate the class name and the file name, but it may not work on Android if they are exactly the same.

Inside that .kv file, you need to specify the layout for your app, including elements like the label, buttons, forms, etc. To keep this demonstration simple, I’ll add a label for the title Random Number, a label that will serve as a placeholder for the random number that is generated _, and a Generate button that calls the generate function.

My .kv file looks like the code below, but you can mess around with the different values to fit your requirements:

<boxLayout>:
    orientation: "vertical"
    Label:
        text: "Random Number"
        font_size: 30
        color: 0, 0.62, 0.96

    Label:
        text: "_"
        font_size: 30

    Button:
        text: "Generate"
        font_size: 15 

In the main.py file, you no longer need the Label import statement because the Kivy file takes care of your UI. However, you do need to import boxlayout, which you will use in the Kivy file.

In your main file, you need to add the import statement and edit your main.py file to read return BoxLayout() in the build method:

from kivy.uix.boxlayout import BoxLayout

If you run the command above, you should see a simple interface that has the random number title, the _ place holder, and the clickable generate button:

Random Number app rendered

Notice that you didn’t have to import anything for the Kivy file to work. Basically, when you run the app, it returns boxlayout by looking for a file inside the Kivy file with the same name as your class. Keep in mind, this is a simple interface, and you can make your app as robust as you want. Be sure to check out the Kv language documentation.

Generate the random number function

Now that our app is almost done, we’ll need a simple function to generate random numbers when a user clicks the generate button, then render that random number into the app interface. To do so, we’ll need to change a few things in our files.

First, we’ll import the module that we’ll use to generate a random number with import random. Then, we’ll create a function or method that calls the generated number. For this demonstration, I’ll use a range between 0 and 2000. Generating the random number is simple with the random.randint(0, 2000) command. We’ll add this into our code in a moment.

Next, we’ll create another class that will be our own version of the box layout. Our class will have to inherit the box layout class, which houses the method to generate random numbers and render them on the interface:

class MyRoot(BoxLayout):
    def __init__(self):
        super(MyRoot, self).__init__()

Within that class, we’ll create the generate method, which will not only generate random numbers but also manipulate the label that controls what is displayed as the random number in the Kivy file.

To accommodate this method, we’ll first need to make changes to the .kv file . Since the MyRoot class has inherited the box layout, you can make MyRoot the top level element in your .kv file:

<MyRoot>:
    BoxLayout:
        orientation: "vertical"
        Label:
            text: "Random Number"
            font_size: 30
            color: 0, 0.62, 0.96

        Label:
            text: "_"
            font_size: 30

        Button:
            text: "Generate"
            font_size: 15

Notice that you are still keeping all your UI specifications indented in the Box Layout. After this, you need to add an ID to the label that will hold the generated numbers, making it easy to manipulate when the generate function is called. You need to specify the relationship between the ID in this file and another in the main code at the top, just before the BoxLayout line:

<MyRoot>:
    random_label: random_label
    BoxLayout:
        orientation: "vertical"
        Label:
            text: "Random Number"
            font_size: 30
            color: 0, 0.62, 0.96

        Label:
            id: random_label
            text: "_"
            font_size: 30

        Button:
            text: "Generate"
            font_size: 15

The random_label: random_label line basically means that the label with the ID random_label will be mapped to random_label in the main.py file, meaning that any action that manipulates random_label will be mapped on the label with the specified name.

We can now create the method to generate the random number in the main file:

def generate_number(self):
    self.random_label.text = str(random.randint(0, 2000))

# notice how the class method manipulates the text attributre of the random label by a# ssigning it a new random number generate by the 'random.randint(0, 2000)' funcion. S# ince this the random number generated is an integer, typecasting is required to make # it a string otherwise you will get a typeError in your terminal when you run it.

The MyRoot class should look like the code below:

class MyRoot(BoxLayout):
    def __init__(self):
        super(MyRoot, self).__init__()

    def generate_number(self):
        self.random_label.text = str(random.randint(0, 2000))

Congratulations! You’re now done with the main file of the app. The only thing left to do is make sure that you call this function when the generate button is clicked. You need only add the line on_press: root.generate_number() to the button selection part of your .kv file:

<MyRoot>:
    random_label: random_label
    BoxLayout:
        orientation: "vertical"
        Label:
            text: "Random Number"
            font_size: 30
            color: 0, 0.62, 0.96

        Label:
            id: random_label
            text: "_"
            font_size: 30

        Button:
            text: "Generate"
            font_size: 15
            on_press: root.generate_number()

Now, you can run the app.

Compiling our app on Android

Before compiling our app on Android, I have some bad news for Windows users. You’ll need Linux or macOS to compile your Android application. However, you don’t need to have a separate Linux distribution, instead, you can use a virtual machine.

To compile and generate a full Android .apk application, we’ll use a tool called Buildozer. Let’s install Buildozer through our terminal using one of the commands below:

pip3 install buildozer
//
pip install buildozer

Now, we’ll install some of Buildozer’s required dependencies. I am on Linux Ergo, so I’ll use Linux-specific commands. You should execute these commands one by one:

sudo apt update
sudo apt install -y git zip unzip openjdk-13-jdk python3-pip autoconf libtool pkg-config zlib1g-dev libncurses5-dev libncursesw5-dev libtinfo5 cmake libffi-dev libssl-dev

pip3 install --upgrade Cython==0.29.19 virtualenv 

# add the following line at the end of your ~/.bashrc file
export PATH=$PATH:~/.local/bin/

After executing the specific commands, run buildozer init. You should see an output similar to the screenshot below:

Buildozer successful initialization

The command above creates a Buildozer .spec file, which you can use to make specifications to your app, including the name of the app, the icon, etc. The .spec file should look like the code block below:

[app]

# (str) Title of your application
title = My Application

# (str) Package name
package.name = myapp

# (str) Package domain (needed for android/ios packaging)
package.domain = org.test

# (str) Source code where the main.py live
source.dir = .

# (list) Source files to include (let empty to include all the files)
source.include_exts = py,png,jpg,kv,atlas

# (list) List of inclusions using pattern matching
#source.include_patterns = assets/*,images/*.png

# (list) Source files to exclude (let empty to not exclude anything)
#source.exclude_exts = spec

# (list) List of directory to exclude (let empty to not exclude anything)
#source.exclude_dirs = tests, bin

# (list) List of exclusions using pattern matching
#source.exclude_patterns = license,images/*/*.jpg

# (str) Application versioning (method 1)
version = 0.1

# (str) Application versioning (method 2)
# version.regex = __version__ = \['"\](.*)['"]
# version.filename = %(source.dir)s/main.py

# (list) Application requirements
# comma separated e.g. requirements = sqlite3,kivy
requirements = python3,kivy

# (str) Custom source folders for requirements
# Sets custom source for any requirements with recipes
# requirements.source.kivy = ../../kivy

# (list) Garden requirements
#garden_requirements =

# (str) Presplash of the application
#presplash.filename = %(source.dir)s/data/presplash.png

# (str) Icon of the application
#icon.filename = %(source.dir)s/data/icon.png

# (str) Supported orientation (one of landscape, sensorLandscape, portrait or all)
orientation = portrait

# (list) List of service to declare
#services = NAME:ENTRYPOINT_TO_PY,NAME2:ENTRYPOINT2_TO_PY

#
# OSX Specific
#

#
# author = © Copyright Info

# change the major version of python used by the app
osx.python_version = 3

# Kivy version to use
osx.kivy_version = 1.9.1

#
# Android specific
#

# (bool) Indicate if the application should be fullscreen or not
fullscreen = 0

# (string) Presplash background color (for new android toolchain)
# Supported formats are: #RRGGBB #AARRGGBB or one of the following names:
# red, blue, green, black, white, gray, cyan, magenta, yellow, lightgray,
# darkgray, grey, lightgrey, darkgrey, aqua, fuchsia, lime, maroon, navy,
# olive, purple, silver, teal.
#android.presplash_color = #FFFFFF

# (list) Permissions
#android.permissions = INTERNET

# (int) Target Android API, should be as high as possible.
#android.api = 27

# (int) Minimum API your APK will support.
#android.minapi = 21

# (int) Android SDK version to use
#android.sdk = 20

# (str) Android NDK version to use
#android.ndk = 19b

# (int) Android NDK API to use. This is the minimum API your app will support, it should usually match android.minapi.
#android.ndk_api = 21

# (bool) Use --private data storage (True) or --dir public storage (False)
#android.private_storage = True

# (str) Android NDK directory (if empty, it will be automatically downloaded.)
#android.ndk_path =

# (str) Android SDK directory (if empty, it will be automatically downloaded.)
#android.sdk_path =

# (str) ANT directory (if empty, it will be automatically downloaded.)
#android.ant_path =

# (bool) If True, then skip trying to update the Android sdk
# This can be useful to avoid excess Internet downloads or save time
# when an update is due and you just want to test/build your package
# android.skip_update = False

# (bool) If True, then automatically accept SDK license
# agreements. This is intended for automation only. If set to False,
# the default, you will be shown the license when first running
# buildozer.
# android.accept_sdk_license = False

# (str) Android entry point, default is ok for Kivy-based app
#android.entrypoint = org.renpy.android.PythonActivity

# (str) Android app theme, default is ok for Kivy-based app
# android.apptheme = "@android:style/Theme.NoTitleBar"

# (list) Pattern to whitelist for the whole project
#android.whitelist =

# (str) Path to a custom whitelist file
#android.whitelist_src =

# (str) Path to a custom blacklist file
#android.blacklist_src =

# (list) List of Java .jar files to add to the libs so that pyjnius can access
# their classes. Don't add jars that you do not need, since extra jars can slow
# down the build process. Allows wildcards matching, for example:
# OUYA-ODK/libs/*.jar
#android.add_jars = foo.jar,bar.jar,path/to/more/*.jar

# (list) List of Java files to add to the android project (can be java or a
# directory containing the files)
#android.add_src =

# (list) Android AAR archives to add (currently works only with sdl2_gradle
# bootstrap)
#android.add_aars =

# (list) Gradle dependencies to add (currently works only with sdl2_gradle
# bootstrap)
#android.gradle_dependencies =

# (list) add java compile options
# this can for example be necessary when importing certain java libraries using the 'android.gradle_dependencies' option
# see https://developer.android.com/studio/write/java8-support for further information
# android.add_compile_options = "sourceCompatibility = 1.8", "targetCompatibility = 1.8"

# (list) Gradle repositories to add {can be necessary for some android.gradle_dependencies}
# please enclose in double quotes 
# e.g. android.gradle_repositories = "maven { url 'https://kotlin.bintray.com/ktor' }"
#android.add_gradle_repositories =

# (list) packaging options to add 
# see https://google.github.io/android-gradle-dsl/current/com.android.build.gradle.internal.dsl.PackagingOptions.html
# can be necessary to solve conflicts in gradle_dependencies
# please enclose in double quotes 
# e.g. android.add_packaging_options = "exclude 'META-INF/common.kotlin_module'", "exclude 'META-INF/*.kotlin_module'"
#android.add_gradle_repositories =

# (list) Java classes to add as activities to the manifest.
#android.add_activities = com.example.ExampleActivity

# (str) OUYA Console category. Should be one of GAME or APP
# If you leave this blank, OUYA support will not be enabled
#android.ouya.category = GAME

# (str) Filename of OUYA Console icon. It must be a 732x412 png image.
#android.ouya.icon.filename = %(source.dir)s/data/ouya_icon.png

# (str) XML file to include as an intent filters in <activity> tag
#android.manifest.intent_filters =

# (str) launchMode to set for the main activity
#android.manifest.launch_mode = standard

# (list) Android additional libraries to copy into libs/armeabi
#android.add_libs_armeabi = libs/android/*.so
#android.add_libs_armeabi_v7a = libs/android-v7/*.so
#android.add_libs_arm64_v8a = libs/android-v8/*.so
#android.add_libs_x86 = libs/android-x86/*.so
#android.add_libs_mips = libs/android-mips/*.so

# (bool) Indicate whether the screen should stay on
# Don't forget to add the WAKE_LOCK permission if you set this to True
#android.wakelock = False

# (list) Android application meta-data to set (key=value format)
#android.meta_data =

# (list) Android library project to add (will be added in the
# project.properties automatically.)
#android.library_references =

# (list) Android shared libraries which will be added to AndroidManifest.xml using <uses-library> tag
#android.uses_library =

# (str) Android logcat filters to use
#android.logcat_filters = *:S python:D

# (bool) Copy library instead of making a libpymodules.so
#android.copy_libs = 1

# (str) The Android arch to build for, choices: armeabi-v7a, arm64-v8a, x86, x86_64
android.arch = armeabi-v7a

# (int) overrides automatic versionCode computation (used in build.gradle)
# this is not the same as app version and should only be edited if you know what you're doing
# android.numeric_version = 1

#
# Python for android (p4a) specific
#

# (str) python-for-android fork to use, defaults to upstream (kivy)
#p4a.fork = kivy

# (str) python-for-android branch to use, defaults to master
#p4a.branch = master

# (str) python-for-android git clone directory (if empty, it will be automatically cloned from github)
#p4a.source_dir =

# (str) The directory in which python-for-android should look for your own build recipes (if any)
#p4a.local_recipes =

# (str) Filename to the hook for p4a
#p4a.hook =

# (str) Bootstrap to use for android builds
# p4a.bootstrap = sdl2

# (int) port number to specify an explicit --port= p4a argument (eg for bootstrap flask)
#p4a.port =


#
# iOS specific
#

# (str) Path to a custom kivy-ios folder
#ios.kivy_ios_dir = ../kivy-ios
# Alternately, specify the URL and branch of a git checkout:
ios.kivy_ios_url = https://github.com/kivy/kivy-ios
ios.kivy_ios_branch = master

# Another platform dependency: ios-deploy
# Uncomment to use a custom checkout
#ios.ios_deploy_dir = ../ios_deploy
# Or specify URL and branch
ios.ios_deploy_url = https://github.com/phonegap/ios-deploy
ios.ios_deploy_branch = 1.7.0

# (str) Name of the certificate to use for signing the debug version
# Get a list of available identities: buildozer ios list_identities
#ios.codesign.debug = "iPhone Developer: <lastname> <firstname> (<hexstring>)"

# (str) Name of the certificate to use for signing the release version
#ios.codesign.release = %(ios.codesign.debug)s


[buildozer]

# (int) Log level (0 = error only, 1 = info, 2 = debug (with command output))
log_level = 2

# (int) Display warning if buildozer is run as root (0 = False, 1 = True)
warn_on_root = 1

# (str) Path to build artifact storage, absolute or relative to spec file
# build_dir = ./.buildozer

# (str) Path to build output (i.e. .apk, .ipa) storage
# bin_dir = ./bin

#    -----------------------------------------------------------------------------
#    List as sections
#
#    You can define all the "list" as [section:key].
#    Each line will be considered as a option to the list.
#    Let's take [app] / source.exclude_patterns.
#    Instead of doing:
#
#[app]
#source.exclude_patterns = license,data/audio/*.wav,data/images/original/*
#
#    This can be translated into:
#
#[app:source.exclude_patterns]
#license
#data/audio/*.wav
#data/images/original/*
#


#    -----------------------------------------------------------------------------
#    Profiles
#
#    You can extend section / key with a profile
#    For example, you want to deploy a demo version of your application without
#    HD content. You could first change the title to add "(demo)" in the name
#    and extend the excluded directories to remove the HD content.
#
#[app@demo]
#title = My Application (demo)
#
#[app:source.exclude_patterns@demo]
#images/hd/*
#
#    Then, invoke the command line with the "demo" profile:
#
#buildozer --profile demo android debug

If you want to specify things like the icon, requirements, loading screen, etc., you should edit this file. After making all the desired edits to your application, run buildozer -v android debug from your app directory to build and compile your application. This may take a while, especially if you have a slow machine.

After the process is done, your terminal should have some logs, one confirming that the build was successful:

Android successful build

You should also have an APK version of your app in your bin directory. This is the application executable that you will install and run on your phone:

Android .apk in the bin directory

Conclusion

Congratulations! If you have followed this tutorial step by step, you should have a simple random number generator app on your phone. Play around with it and tweak some values, then rebuild. Running the rebuild will not take as much time as the first build.

As you can see, building a mobile application with Python is fairly straightforward, as long as you are familiar with the framework or module you are working with. Regardless, the logic is executed the same way.

Get familiar with the Kivy module and it’s widgets. You can never know everything all at once. You only need to find a project and get your feet wet as early as possible. Happy coding.

Link: https://blog.logrocket.com/build-android-application-kivy-python-framework/

#python