Skip to content

Commit fecf8d4

Browse files
committed
wip: added new file with common functions, and created a new one to merge vehicle detections and license plate detections
1 parent ab610e9 commit fecf8d4

File tree

4 files changed

+296
-3
lines changed

4 files changed

+296
-3
lines changed

.pylintrc

+7-3
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,15 @@
22
extension-pkg-whitelist=
33
cv2.*,
44
dai.*,
5-
depthai.*
5+
depthai.*,
6+
utils.*,
67

78
[TYPECHECK]
89
generated-members=
910
cv2.*,
1011
dai.*,
11-
depthai.*
12+
depthai.*,
13+
utils.*,
1214

1315
[FORMAT]
1416
max-line-length=240
@@ -17,4 +19,6 @@ max-line-length=240
1719
disable=
1820
C0114, # missing-module-docstring
1921
C0116, # missing-function-docstring
20-
I1101 # extension-pkg-allow-list
22+
I1101, # extension-pkg-allow-list
23+
W0621, # redefined-outer-name
24+
E0611, # no-name-in-module

README.md

+6
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,12 @@ License plate detections
5858
anpr-luxonis-python git:(main) python3 main.py -nn ~/ai/custom_models/frozen_inference_graph_openvino_2021.4_6shave.blob -vid ~/Videos/1.mp4
5959
```
6060

61+
Car detections
62+
63+
```bash
64+
python3 car_detect.py -vid ~/Videos/1.mp4
65+
```
66+
6167
## Pretrained models
6268

6369
Luxonis zoo

car_lp_detect.py

+186
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
#!/usr/bin/env python3
2+
3+
import argparse
4+
from pathlib import Path
5+
6+
import blobconverter
7+
import cv2
8+
import depthai as dai
9+
from depthai_sdk import FPSHandler
10+
11+
from utils import frame_norm, send_frame_to_queue
12+
13+
parser = argparse.ArgumentParser()
14+
parser.add_argument("-d", "--debug", default=True, help="Debug mode")
15+
parser.add_argument("-cam", "--camera", action="store_true", help="Use DepthAI 4K RGB camera for inference (conflicts with -vid)")
16+
parser.add_argument("-vid", "--video", type=argparse.FileType("r", encoding="UTF-8"), help="Path to video file to be used for inference (conflicts with -cam)")
17+
parser.add_argument("-nn", "--nn-blob-model", type=argparse.FileType("r", encoding="UTF-8"), help="Set path of the blob (NN model)")
18+
parser.add_argument("-nnt", "--nn-threshold", type=float, default=0.5, help="Neural Networks Confidence Thresholds")
19+
args = parser.parse_args()
20+
21+
if not args.camera and not args.video:
22+
raise RuntimeError('No source selected. Use either "-cam" to run on RGB camera as a source or "-vid <path>" to run on video')
23+
24+
VEH_NN_INPUT_IMG_WIDTH = 256
25+
VEH_NN_INPUT_IMG_HEIGHT = 256
26+
27+
LP_NN_INPUT_IMG_WIDTH = 300
28+
LP_NN_INPUT_IMG_HEIGHT = 300
29+
30+
SHAVES = 6 if args.camera else 8
31+
32+
pipeline = dai.Pipeline()
33+
34+
# cam/vid -> veh_manip -> veh_nn -> lp_nn
35+
36+
if args.camera:
37+
cam = pipeline.create(dai.node.ColorCamera)
38+
cam.setPreviewSize(1024, 768)
39+
cam.setResolution(dai.ColorCameraProperties.SensorResolution.THE_1080_P)
40+
cam.setInterleaved(False)
41+
cam.setBoardSocket(dai.CameraBoardSocket.RGB)
42+
else:
43+
# create a XLinkIn to send the video frames from video file
44+
vid = pipeline.create(dai.node.XLinkIn)
45+
vid.setStreamName("vid")
46+
vid.setNumFrames(4)
47+
# create a video file capture
48+
cap = cv2.VideoCapture(str(Path(args.video.name).resolve().absolute()))
49+
50+
# Vehicle detection NN
51+
veh_nn = pipeline.createMobileNetDetectionNetwork()
52+
veh_nn.setBlobPath(blobconverter.from_zoo(name="vehicle-detection-0200", shaves=SHAVES))
53+
veh_nn.setConfidenceThreshold(args.nn_threshold)
54+
veh_nn.setNumInferenceThreads(2)
55+
veh_nn.input.setQueueSize(1)
56+
57+
# license plate detection NN
58+
lp_nn = pipeline.createMobileNetDetectionNetwork()
59+
lp_nn.setBlobPath(args.nn_blob_model.name)
60+
lp_nn.setConfidenceThreshold(args.nn_threshold)
61+
lp_nn.setNumInferenceThreads(2)
62+
lp_nn.input.setQueueSize(1)
63+
64+
# ImageManip will resize the frame coming from the camera/video
65+
# before sending it to the license plate detection NN node
66+
veh_manip = pipeline.create(dai.node.ImageManip)
67+
veh_manip.initialConfig.setResize(VEH_NN_INPUT_IMG_WIDTH, VEH_NN_INPUT_IMG_HEIGHT)
68+
veh_manip.initialConfig.setFrameType(dai.RawImgFrame.Type.BGR888p)
69+
veh_manip.out.link(veh_nn.input)
70+
71+
# use if we want to run the license plate NN in parallel with the veh NN
72+
# lic_manip = pipeline.create(dai.node.ImageManip)
73+
# lic_manip.initialConfig.setResize(LP_NN_INPUT_IMG_WIDTH, LP_NN_INPUT_IMG_HEIGHT)
74+
# lic_manip.initialConfig.setFrameType(dai.RawImgFrame.Type.BGR888p)
75+
# lic_manip.out.link(lp_nn.input)
76+
77+
# any frame sent to in_veh queue will have the dimensions and characteristics
78+
# required by the license plate NN
79+
# in_veh.out.link(lic_manip.input) # this is in case we will pre-process the image before
80+
in_veh = pipeline.create(dai.node.XLinkIn)
81+
in_veh.setStreamName("in_veh")
82+
in_veh.out.link(lp_nn.input)
83+
84+
85+
# Send cam/vid to the host
86+
xout_rgb = pipeline.create(dai.node.XLinkOut)
87+
xout_rgb.setStreamName("rgb")
88+
89+
# Send detections to the host (for bounding boxes)
90+
xout_veh_det = pipeline.create(dai.node.XLinkOut)
91+
xout_veh_det.setStreamName("veh_det")
92+
93+
xout_lp_det = pipeline.create(dai.node.XLinkOut)
94+
xout_lp_det.setStreamName("lp_det")
95+
96+
# connect detections to xout
97+
veh_nn.out.link(xout_veh_det.input)
98+
lp_nn.out.link(xout_lp_det.input)
99+
100+
# connect cam/vid to xout to see these as it is and
101+
# send the source frames (cam/vid) to the image manipulation,
102+
# to resize the image frame according to the NN
103+
if args.camera:
104+
cam.preview.link(xout_rgb.input)
105+
cam.preview.link(veh_manip.inputImage)
106+
else:
107+
vid.out.link(xout_rgb.input)
108+
vid.out.link(veh_manip.inputImage)
109+
110+
# to manage the frames rate
111+
if args.camera:
112+
fps = FPSHandler()
113+
else:
114+
fps = FPSHandler(cap)
115+
116+
117+
def should_run() -> bool:
118+
"""
119+
This is needed to validate if the video is
120+
loaded, for camera always is true
121+
"""
122+
return cap.isOpened() if args.video else True
123+
124+
125+
# def veh_thread(detect_queue: dai.DataOutputQueue, send_queue: dai.DataInputQueue) -> None:
126+
127+
# while RUNNING:
128+
# try:
129+
# detections = []
130+
# in_queue = detect_queue.get()
131+
# if in_queue is not None:
132+
# detections = in_queue.detections
133+
134+
# for det in detections:
135+
# bbox = frame_norm(orig_frame, (detection.xmin, detection.ymin, detection.xmax, detection.ymax))
136+
137+
# except RuntimeError:
138+
# continue
139+
140+
141+
with dai.Device(pipeline) as device:
142+
RUNNING = True
143+
144+
veh_det = device.getOutputQueue("veh_det", 1, False) # to get the detection from vehicle NN
145+
lp_det = device.getOutputQueue("lp_det", 1, False) # to get the detection from license plate NN
146+
q_rgb = device.getOutputQueue("rgb", 1, True) # to get the frame processed
147+
148+
q_veh = device.getInputQueue("in_veh") # to send the frames cropped with vehicle detection box
149+
q_vid = device.getInputQueue("vid") # to send the frames coming from video file
150+
151+
veh_detections = []
152+
lp_detections = []
153+
154+
if not args.camera:
155+
# size of frames coming from video file
156+
video_width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
157+
video_height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
158+
159+
while should_run():
160+
if not args.camera:
161+
send_frame_to_queue(cap, q_vid, (video_width, video_height))
162+
163+
frame = q_rgb.get().getCvFrame()
164+
# sequence = q_rgb.getSequenceNum()
165+
166+
veh_det_data = veh_det.tryGet()
167+
if veh_det_data is not None:
168+
veh_detections = veh_det_data.detections
169+
170+
fps.nextIter()
171+
if frame is not None:
172+
for veh in veh_detections:
173+
bbox = frame_norm(frame, (veh.xmin, veh.ymin, veh.xmax, veh.ymax))
174+
cv2.rectangle(frame, (bbox[0], bbox[1]), (bbox[2], bbox[3]), (0, 255, 0), 2)
175+
176+
cv2.putText(frame, "Fps: {:.2f}".format(fps.fps()), (2, frame.shape[0] - 4), cv2.FONT_HERSHEY_TRIPLEX, 0.8, color=(0, 255, 0))
177+
cv2.imshow("preview", frame)
178+
179+
if cv2.waitKey(1) == ord("q"):
180+
break
181+
182+
RUNNING = False
183+
184+
print("FPS: {:.2f}".format(fps.fps()))
185+
if not args.camera:
186+
cap.release()

utils.py

+97
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
import time
2+
3+
import cv2
4+
import depthai as dai
5+
import numpy as np
6+
7+
8+
def frame_norm(frame: object, bbox: tuple) -> np.ndarray:
9+
"""
10+
This create a norm
11+
12+
Parameters
13+
----------
14+
frame: np.ndarray
15+
Object created with cv2.VideoCapture().read()
16+
17+
bbox: tuple
18+
Tuple
19+
20+
21+
Returns
22+
-------
23+
np.ndarray
24+
Numpy NDArray object that represent the transformation of the arr after
25+
apply shape
26+
"""
27+
norm_vals = np.full(len(bbox), frame.shape[0])
28+
norm_vals[::2] = frame.shape[1]
29+
return (np.clip(np.array(bbox), 0, 1) * norm_vals).astype(int)
30+
31+
32+
def to_planar(arr: np.ndarray, shape: tuple) -> np.ndarray:
33+
"""
34+
Transform a np.ndarray (cv2.VideoCapture().read())
35+
36+
Parameters
37+
----------
38+
frame: np.ndarray
39+
Object created with cv2.VideoCapture().read()
40+
41+
shape: tuple
42+
Tuple
43+
44+
45+
Returns
46+
-------
47+
np.ndarray
48+
Numpy NDArray object that represent the transformation of the arr after
49+
apply shape
50+
"""
51+
return cv2.resize(arr, shape).transpose(2, 0, 1).flatten()
52+
53+
54+
def to_depthai_frame(frame: np.ndarray, size: tuple) -> dai.ImgFrame:
55+
"""
56+
Transform a np.ndarray (cv2.VideoCapture().read()) frame to a depthai.ImgFrame
57+
changing it to BGR888p format given the size tuple represented by (width,height)
58+
59+
Parameters
60+
----------
61+
frame: np.ndarray
62+
Object created with cv2.VideoCapture().read()
63+
64+
size: tuple
65+
Tuple with the width and heigh used to create the depthai.ImgFrame
66+
"""
67+
time_stamp = time.monotonic()
68+
img = dai.ImgFrame()
69+
img.setData(to_planar(frame, size))
70+
img.setTimestamp(time_stamp)
71+
img.setType(dai.RawImgFrame.Type.BGR888p)
72+
img.setWidth(size[0])
73+
img.setHeight(size[1])
74+
75+
return img
76+
77+
78+
def send_frame_to_queue(video_capture: np.ndarray, send_queue: dai.DataInputQueue, size: tuple) -> None:
79+
"""
80+
Send frame read from cv2.VideoCapture() to a dai.DataInputQueue after transform this to a depthai.ImgFrame()
81+
82+
Parameters
83+
----------
84+
video_capture: np.ndarray
85+
Object created with cv2.VideoCapture()
86+
87+
send_queue: dai.DataInputQueue
88+
Object created with depthai.pipeline.create(dai.node.XLinkIn)
89+
90+
size: tuple, required
91+
Tuple with the width and heigh used to create the depthai.ImgFrame
92+
before send this to send_queue
93+
"""
94+
success, frame = video_capture.read()
95+
if success:
96+
img_frame = to_depthai_frame(frame, size)
97+
send_queue.send(img_frame)

0 commit comments

Comments
 (0)