#Coding4Fun – How to control your #drone ✈ with 20 lines of code! (24/N) #AzureIoT

Buy Me A Coffee

Hi !

Final post, so let’s recap

We have a device template representing a drone, tracking accelerometer, battery and temperature values.

We have created a new device to twin our drone in Azure IoT Central. And, copy the connect information: ScopeId, DeviceId and Key.

Let’s use a “get data from drone demo” and publish this information to Azure IoT Central.

I’ll share the complete file below, however let’s take a look at the main app code.

  • The following code block iterates 300 times while is reading drone information.
  • In each iteration, it send to the drone devices the accelerometer values. These are drone telemetry capabilities.
  • Every 10 iterations, it also update other drone capabilities: battery and temperature. These are drone properties.
# MAIN APP
battery = 0
agx     = 0
agy     = 0
agz     = 0
temph   = 0
templ   = 0

i = 0
while i < 300:
    i = i + 1
    sendReadCommand('battery?')
    await drone.send_telemetry(agx, agy, agz)
    if (i % 10) == 0:
        await drone.send_properties(temph, templ, battery)
    time.sleep(1)

Once we run the app, Azure IoT Central dashboard will show the real-time information from the drone. If the drone is flying and reporting accelerometer values, the agx, agy, agz chart will display the values related to axis x, y and z.

drone azure iot central out of battery

Disclaimer: I declared the temperature unit as Celsius. You can quickly realize that the values are in Farenheight.

And as promised here is the full code for this demo.

# Copyright (c) 2020
# Author: Bruno Capuano
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#
# ———————————————–
# 70 AZURE IOT HUB Sample
# ———————————————–
import socket
import time
import threading
import iotc
import provision_service
import asyncio
import json
import datetime
from drone_device import Drone_Device
# ———————————————–
# RECEIVE DATA FUNCTIONS
# ———————————————–
def receiveData():
global response, clientSocket
while True:
try:
response, _ = clientSocket.recvfrom(1024)
except:
break
def readStates():
global battery, agx, agy, agz, temph, templ
global response, response_state, clientSocket, stateSocket, address
while True:
try:
response_state, _ = stateSocket.recvfrom(256)
if response_state != 'ok':
response_state = response_state.decode('ASCII')
list = response_state.replace(';', ':').split(':')
battery = int(list[21])
agx = float(list[27])
agy = float(list[29])
agz = float(list[31])
temph = int(list[15])
templ = int(list[13])
# except:
# break
except Exception as e:
print(f'exc: {e}')
pass
# ———————————————–
# SEND COMMAND FUNCTIONS
# ———————————————–
def sendCommand(command):
global response, clientSocket, address
timestamp = int(time.time() * 1000)
clientSocket.sendto(command.encode('utf-8'), address)
while response is None:
if (time.time() * 1000) timestamp > 5 * 1000:
return False
return response
def sendReadCommand(command):
global response
response = sendCommand(command)
try:
response = str(response)
except:
pass
return response
def sendControlCommand(command):
global response, response_state, clientSocket, stateSocket, address
response = None
for i in range(0, 5):
response = sendCommand(command)
if response == 'OK' or response == 'ok':
return True
return False
# ———————————————–
# AZURE IOT CENTRAL
# ———————————————–
async def init_drone_AzureIoT():
global drone
iothub = ""
scope = "Azure IoT Central Device Connect Scope ID goes here"
device_id = "Azure IoT Central Device Connect Device ID goes here"
key = "Azure IoT Central Device Connect KEY goes here"
drone = Drone_Device(scope, device_id, key)
await drone.init_azureIoT()
# ———————————————–
# 35 CAMERA
# APP DISPLAY CAMERA WITH OPENCV and FPS
# ———————————————–
async def main():
global battery, agx, agy, agz, temph, templ
global response, response_state, clientSocket, stateSocket, address
global drone
await init_drone_AzureIoT()
# CONNECTION TO THE DRONE
# connection info
UDP_IP = '192.168.10.1'
UDP_PORT = 8889
last_received_command = time.time()
STATE_UDP_PORT = 8890
address = (UDP_IP, UDP_PORT)
response = None
response_state = None
clientSocket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
clientSocket.bind(('', UDP_PORT))
stateSocket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
stateSocket.bind(('', STATE_UDP_PORT))
# LISTENER THREADS
# start threads
recThread = threading.Thread(target=receiveData)
recThread.daemon = True
recThread.start()
stateThread = threading.Thread(target=readStates)
stateThread.daemon = True
stateThread.start()
# START DRONE CONNECTION
# connect to drone
response = sendControlCommand("command")
print(f'command response: {response}')
response = sendControlCommand("streamon")
print(f'streamon response: {response}')
# MAIN APP
# drone information
battery = 0
agx = 0
agy = 0
agz = 0
temph = 0
templ = 0
i = 0
while i < 300:
i = i + 1
sendReadCommand('battery?')
await drone.send_telemetry(agx, agy, agz)
if (i % 10) == 0:
await drone.send_properties(temph, templ, battery)
time.sleep(1)
if __name__ == "__main__":
asyncio.run(main())

In the next post we will connect everything together !

Happy coding!

Greetings

El Bruno


References


¿Con ganas de ponerte al día?

En Lemoncode te ofrecemos formación online impartida por profesionales que se baten el cobre en consultoría:

#Coding4Fun – How to control your #drone ✈ with 20 lines of code! (23/N) #AzureIoT

Buy Me A Coffee

Hi !

Now that we have a device template created, we can create a new device in our Azure IoT Central portal and start to send information to Azure IoT.

We start by creating a new device based on the previous template and it’s time to copy and paste the connection information for this device:

  • ID scope
  • Device ID
  • Primary Key
drone connect information

And, based on the official Python SDK documentation, I created this full class to send information to Azure IoT Central. A couple of notes

  • The class needs scopeID, deviceID and key in the constructor to create the device connection string
  • There are 2 main functions to showcase how to send telemetry and update properties
  • Telemetry (send_telemetry) uses the predefined device capabilities: agx, agy and agz
  • Properties (send_properties) uses the predefined device capabilities: battery, templ and temph
# Copyright (c) 2020
# Author: Bruno Capuano
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#
import os
import datetime
import asyncio
import json
from azure.iot.device.aio import IoTHubDeviceClient
from azure.iot.device import MethodResponse
from azure.iot.device.aio import ProvisioningDeviceClient
class Drone_Device():
def __init__(self, scope, device_id, key, iothub : str = ""):
self.scope = scope
self.device_id = device_id
self.key = key
self.iothub = iothub
async def init_azureIoT(self):
cnn_str = await self.get_connection_string()
self.device_client = IoTHubDeviceClient.create_from_connection_string(cnn_str)
await self.device_client.connect()
async def __register_device(self):
provisioning_device_client = ProvisioningDeviceClient.create_from_symmetric_key(
provisioning_host='global.azure-devices-provisioning.net',
registration_id=self.device_id,
id_scope=self.scope,
symmetric_key=self.key,
)
return await provisioning_device_client.register()
async def get_connection_string(self):
if(self.iothub == None or self.iothub == ""):
print(f'{datetime.datetime.now()}: No IOTHUB specified. Attempting to resolve via global.azure-devices-provisioning.net')
results = await asyncio.gather(self.__register_device())
print(results)
registration_result = results[0]
cnn_str = 'HostName=' + registration_result.registration_state.assigned_hub + \
';DeviceId=' + self.device_id + \
';SharedAccessKey=' + self.key
else:
cnn_str = 'HostName=' + self.iothub + \
';DeviceId=' + self.device_id + \
';SharedAccessKey=' + self.key
print(f'{datetime.datetime.now()}: Connection String = {cnn_str}')
return cnn_str
async def send_telemetry(self, agx, agy, agz):
try:
payload:str = ""
data = {
"agx": agx,
"agy": agy,
"agz": agz
}
payload = json.dumps(data)
print(f"{datetime.datetime.now()}: telemetry: {payload}")
await self.device_client.send_message(payload)
except Exception as e:
print(f"{datetime.datetime.now()}: Exception during sending metrics: {e}")
async def send_properties(self, bat, temph, templ):
try:
data = {
'bat': bat,
'templ': templ,
'temph': temph
}
propertiesToUpdate = data
print(f"{datetime.datetime.now()}: properties: {propertiesToUpdate}")
await self.device_client.patch_twin_reported_properties(propertiesToUpdate)
except Exception as e:
print(f"{datetime.datetime.now()}: Exception during sending metrics: {e}")

In the next post we will connect everything together !

Happy coding!

Greetings

El Bruno


References


¿Con ganas de ponerte al día?

En Lemoncode te ofrecemos formación online impartida por profesionales que se baten el cobre en consultoría:

#Python – Working with dates 📅, formats, and subtract for time difference. Timedelta type rocks!

Buy Me A Coffee

Hi !

I’m switching from C#9 to Python to write this down, so I avoid to search and write this from scratch again and again. My scenario includes very simple operations with dates,

  • Tag a start date time
  • Do some process, I’ll fake it with random sleep
  • Tag a end date time
  • Calculate the different between them
  • Process and show the difference with a specific format

For this I’ll use the datetime type, and a cool feature using datetime is that we can directly substract 2 dates. In example:

start = datetime.datetime.utcnow()
time.sleep(10)
end = datetime.datetime.utcnow()

delta = end - start

In this code 👆, the delta variable is a timedelta type. This is not a standard datetime, in C# we know this as TimeSpan. We can access the internal values, and get some details around hours, minutes, seconds and millisecons:

def get_timedelta_values(delta):
    # Get the hours, minutes, seconds and milliseconds
    millis           = round(delta.microseconds/1000, 0)
    minutes, seconds = divmod(delta.seconds, 60)
    hours, minutes   = divmod(minutes, 60)
    return hours, minutes, seconds, millis    

def get_timedelta_min_and_sec(delta):
    hours, minutes, seconds, millis = get_timedelta_values(delta)
    return str(f'{str(minutes).zfill(2)}:{str(seconds).zfill(2)}')

The 2nd function, get the delta values and creates a standard output with “mm:ss” format. So, not tricky and easy to move forward.

Another cool feature of the timedelta is that support adding. So as a final example, I’ll generate some random deltas in a loop, and add them in a totalTime var.

import time
import datetime
from random import randint

def get_timedelta_values(delta):
    # Get the hours, minutes, seconds and milliseconds
    millis           = round(delta.microseconds/1000, 0)
    minutes, seconds = divmod(delta.seconds, 60)
    hours, minutes   = divmod(minutes, 60)
    return hours, minutes, seconds, millis    

def get_timedelta_min_and_sec(delta):
    hours, minutes, seconds, millis = get_timedelta_values(delta)
    return str(f'{str(minutes).zfill(2)}:{str(seconds).zfill(2)}')

totalTime = None
i = 0
while i < 25:
    i = i + 1
    rndSleep = randint(1, 10)
    print(f'current iteration: {i} - rnd sleep: {rndSleep}')

    start = datetime.datetime.utcnow()
    time.sleep(rndSleep)
    end = datetime.datetime.utcnow()

    delta = end - start

    if (totalTime is None):
        totalTime = delta
    else:
        totalTime += delta

    print(f'  >> delta     : {get_timedelta_min_and_sec(delta)}')
    print(f'  >> total time: {get_timedelta_min_and_sec(totalTime)}')

The output as expected shows the delta with specific format in each iteration and also the accumulator for the total time

python adding time delta vars

So, here it is, a simple example that will save me some time in the future for sure !

Happy coding!

Greetings

El Bruno



¿Con ganas de ponerte al día?

En Lemoncode te ofrecemos formación online impartida por profesionales que se baten el cobre en consultoría:

References

#Net5 – C#9 “records” and “deconstruction” super cool feature 🆒🆒🆒

Buy Me A Coffee

Hi !

I’m still learning about C#9, and there is a lot to learn about records. And, between all this information, I Just learn a super cool feature related to records: deconstruction.

Let’s start with a simple record definition for a pet. This one includes pet’s name and pet’s age:

public record Pet
{
    public string Name { get; init; }
    public int Age { get; init; }
    public Pet(string name, int age)
        => (Name, Age) = (name, age);
}

This is fine, init only properties (cool feature!) and we can access the values as usual.

static void Main(string[] args)
{
    var pet = new Pet("Goku", 2);

    // get pet info, standard way
    var gokuName = pet.Name;
    var gokuAge = pet.Age;
}

So far, so good. However, if you like clean code, we can improve this a little using some new features in C#9. So, let’s start with much simpler record definition.

public record NewPet(string Name, int Age);

And we can access the pet properties using deconstruction (new term for me!)

// get pet info, C#9 deconstruction
var newPet = new NewPet("Goku", 2);
var (gokuName, gokuAge) = newPet;

I like this new one 👆 too ! I’ll always like features that improves readability and saves us a lot of extra code.

Happy coding!

Greetings

El Bruno


References


¿Con ganas de ponerte al día?

En Lemoncode te ofrecemos formación online impartida por profesionales que se baten el cobre en consultoría:

#Net5 – C#9 “Init-only properties” are super cool 🆒🆒🆒

Buy Me A Coffee

Hi !

So this one does not fit in the line “readability improvement”, however is a nice step in order to write cleaner code. Yes, I know that doesn’t make sense, let me try to explain.

Let’s start with a simple class with 2 properties. Interesting enough the property Age has a new accessor [init] to describe the property as a read-only property:

class Pet
{
  public string Name { get; set; }
  public int Age { get; init; }
}

When we create a new Pet object, we can set the initial value of Age on the construction of the object (object initialization), and that’s it. Otherwise we will get this amazing error.

Error CS8852 Init-only property or indexer 'Program.Pet.Age' can only be assigned in an object initializer, or on 'this' or 'base' in an instance constructor or an 'init' accessor. 
Csharp9 init only properties

Until C#9, the properties in a class need to be mutable in order to support Object Initialization. And that means some extra code in the property-set definition in order to support read-only properties. Now in C# 9, this is solved with the [init] accessor 😁😁😁

I like this new one 👆 ! Not a great readability improvement, but it saves us a lot of extra code.

Happy coding!

Greetings

El Bruno


References


¿Con ganas de ponerte al día?

En Lemoncode te ofrecemos formación online impartida por profesionales que se baten el cobre en consultoría:

#Net5 – C#9 logical operators “is” and “is not” are super cool 🆒🆒🆒

Buy Me A Coffee

Hi !

In the line of good features, that are also nice to read, the new use of is and is not is a huge improvement in readability.

As usual, old school validation for objects and types:

// old school
if(!(testObj is MyClass)) {...}

And now, we can type the validation with some style:

// C# 9 style
if(testObj is not MyClass) {...}

I like this new one 👆, now null / not null and other validations will be the same. With an improed readability !

Happy coding!

Greetings

El Bruno


References


¿Con ganas de ponerte al día?

En Lemoncode te ofrecemos formación online impartida por profesionales que se baten el cobre en consultoría:

#Net5 – C#9 target-typed new expressions are super cool 🆒🆒🆒

Buy Me A Coffee

Hi !

I know I’m late to the party here, however I’m still enjoying some of the new C# 9 features a lot. I just realized that now, I can create objects in a different way, which is still super clear to read.

Disclaimer: some of the new features are cool, however my gut told me that they will make code harder to read. I also understand that I have 20 years of writing C# code in my back, so I need to find the balance here.

As usual, 2 lines of code are the best way to showcase this:

// old school
var pointOldSchool = new System.Drawing.Point(3, 5);

// new school and still very readable
System.Drawing.Point pointCSharpNine = new(3, 5);

// crappy output
Console.WriteLine("Old School: var pointOldSchool = new System.Drawing.Point(3, 5);");
Console.WriteLine(pointOldSchool);

Console.WriteLine("C#9: System.Drawing.Point pointCSharpNine = new(3, 5);");
Console.WriteLine(pointCSharpNine);

This this output as expected:

Old School: var pointOldSchool = new System.Drawing.Point(3, 5);
{X=3,Y=5}
C#9: System.Drawing.Point pointCSharpNine = new(3, 5);
{X=3,Y=5}

I like this new one 👆

Happy coding!

Greetings

El Bruno


References


¿Con ganas de ponerte al día?

En Lemoncode te ofrecemos formación online impartida por profesionales que se baten el cobre en consultoría:

#Net5 – C#9 “records”, “with” and “this”, a super cool mix 🆒🆒🆒

Buy Me A Coffee

Hi !

Our next podcast episode is focused on .Net 5. We had an amazing chat around a lot of features, and of course, C# 9 was part of the conversation.

I got this amazing post [C# 9.0: Records – Work With Immutable Data Classes] from Tomas Huber, in my reading notes and working with records is something that I wanted to test.

In a few words, a record is an immutable class. Which is super cool, because it allows some cool operations for mapping, reflection and more. Again, please read Tomas post.

On of the features of records is that once, you define a record with a constructor, there is no default constructor anymore. Let’s start with a simple person record with 3 properties for Name, Age and Married status (as boolean)

public record PersonRecord(string Name, int Age, bool Married)
{
    public override string ToString() => 
        $"Name: {Name} - Age: {Age} - Married: {Married}";
}

Losing the default constructor can be a challenge if we want to copy a record. However we can take advantage of the with expression and create new objects that uses the protected copy constructor. Let’s take a look at the same person object with a CopyUsingMarriedTrue() function that, creates a new object with the same properties changing the value of the Married property.

public record PersonRecord(string Name, int Age, bool Married = false)
{
    public Guid Id { get; init; } = Guid.NewGuid();
    public override string ToString() => 
        $"Name: {Name} - Age: {Age} - Married: {Married} - Guid: {Id}";

    public PersonRecord CopyUsingMarriedTrue() => this with { Married = true };
}

Now it’s time for a full Console test, where I create a couple of persons and check the values of the person. Important, I added a Guid Id property to test the copy and constructor behavior.

  • Lines 14 to 18, I created a Bruno person with Married as True, and I created a BrunoMarried to check the new created object.
  • Lines 20 to 25, I created a Valentino person with Married as False and I created a ValentinoMarried to check the new created object.
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Security.Cryptography.X509Certificates;
namespace ConsoleApp3
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("start");
var bruno = new PersonRecord("Bruno", 40, true);
Console.WriteLine($"{bruno.ToString()}");
Console.WriteLine($"Bruno Is Married created");
var brunoMarried = bruno.CopyUsingMarriedTrue();
Console.WriteLine($"{brunoMarried }");
Console.WriteLine($"———————————–");
var valentino = new PersonRecord("Valentino", 13, false);
Console.WriteLine($"{valentino.ToString()}");
Console.WriteLine($"Valentino Is Married created");
var valentinoMarried = valentino.CopyUsingMarriedTrue();
Console.WriteLine($"{valentinoMarried}");
}
}
public record PersonRecord(string Name, int Age, bool Married = false)
{
public Guid Id { get; init; } = Guid.NewGuid();
public override string ToString() => $"Name: {Name} – Age: {Age} – Married: {Married} – Guid: {Id}";
public PersonRecord CopyUsingMarriedTrue() => this with { Married = true };
}
}

The output is an awesome surprise. I mean, my code always surprise me, however using this feature is an awesome surprise !

start
Name: Bruno - Age: 40 - Married: True - Guid: 82e60022-0fb2-456d-89c0-ab88bef5aff6
Bruno Is Married created
Name: Bruno - Age: 40 - Married: True - Guid: 82e60022-0fb2-456d-89c0-ab88bef5aff6
-----------------------------------
Name: Valentino - Age: 13 - Married: False - Guid: 7acca9e2-f461-4fc9-8e29-06f6f0ffb6c6
Valentino Is Married created
Name: Valentino - Age: 13 - Married: True - Guid: 7acca9e2-f461-4fc9-8e29-06f6f0ffb6c6
csharp 9 with and records

Happy coding!

Greetings

El Bruno


References


¿Con ganas de ponerte al día?

En Lemoncode te ofrecemos formación online impartida por profesionales que se baten el cobre en consultoría:

#OpenCV – Open a video file 🎥 and save each frame as a PNG 🖼 file to a folder 📂#Net5

Buy Me A Coffee

Hi !

A couple of days ago I wrote this post, and made the sample with Python. So today, same scenario, however with C# 9 and Net5.

This is a non-usual scenario, however I’m on a point where I need to extract all the frames from a video file. The reason: some of these frames will be used to train a Machine Learning model.

There are tools that can do this, however it’s a nice moment to do some OpenCV code. Let’s go for it. A couple of remarks

  • Video file must be in the same folder as python file, and the name is defined in video_file, line 7
  • There is a live preview of the video, comment line 24 to avoid this
  • You can stop the process at any time pressing the Q letter
using System;
using OpenCvSharp;

var videoFile = "01.mp4";
System.IO.Directory.CreateDirectory("frames");

var capture = new VideoCapture(videoFile);
var window = new Window("El Bruno - OpenCVSharp Save Video Frame by Frame");
var image = new Mat();

var i = 0;
while (capture.IsOpened())
{
    capture.Read(image);
    if (image.Empty())
        break;

    i++;
    var imgNumber = i.ToString().PadLeft(8, '0');

    var frameImageFileName = $@"frames\image{imgNumber}.png";
    Cv2.ImWrite(frameImageFileName, image);

    window.ShowImage(image);
    if (Cv2.WaitKey(1) == 113) // Q
        break;
}

Console.WriteLine("Complete !");

Happy coding!

Greetings

El Bruno


#OpenCV – Open a video file 🎥 and save each frame as a PNG 🖼 file to a folder 📂#Python

Buy Me A Coffee

Hi !

This is a non-usual scenario, however I’m on a point where I need to extract all the frames from a video file. The reason: some of these frames will be used to train a Machine Learning model.

There are tools that can do this, however it’s a nice moment to do some OpenCV code. Let’s go for it. A couple of remarks

  • Video file must be in the same folder as python file, and the name is defined in video_file, line 11
  • I resize the frame to 640 x 480, remove line 20 and fix vars name
  • There is a live preview of the video, comment line 27 to avoid this
  • You can stop the process at any time pressing the Q letter
# Bruno Capuano 2020
# open a video file and save each frame as a PNG file to a folder

import cv2
import os

# Camera Settings
camera_Width  = 640 # 1024 # 1280 # 640
camera_Heigth = 480 # 780  # 960  # 480
frameSize = (camera_Width, camera_Heigth)
video_file = "03.mp4"
video_capture = cv2.VideoCapture(video_file)

i = 0
while video_capture.isOpened():

    ret, frameOrig = video_capture.read()
    if ret == True:
        # resize frame, optional you may not need this
        frame = cv2.resize(frameOrig, frameSize)

        i += 1
        imgNumber = str(i).zfill(5)
        frameImageFileName = str(f'03\image{imgNumber}.png')
        cv2.imwrite(frameImageFileName, frameOrig)

        cv2.imshow('Video', frame)
    else:
        break

    # key controller
    key = cv2.waitKey(1) & 0xFF    
    if key == ord("q"):
        break

video_capture.release()
cv2.destroyAllWindows()

Happy coding!

Greetings

El Bruno