Visualizzazione post con etichetta Opencv. Mostra tutti i post
Visualizzazione post con etichetta Opencv. Mostra tutti i post

venerdì 22 marzo 2024

OpenCV e camera UVC

Ho scoperto per caso che le camere UVC sono facilmente controllabili da OpenCV  in particolare per quanto riguardo il parametro dell'auto esposizione

Per le caratteristiche della camera si usa 

v4l2-ctl --list-formats-ext -d 0

con il parametro d che e' il numero del device

 

===============================================

import cv2

cap = cv2.VideoCapture(0)

# The control range can be viewed through v4l2-ctl -L
cap.set(cv2.CAP_PROP_FRAME_WIDTH, 1920)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 1080)
cap.set(cv2.CAP_PROP_BRIGHTNESS, 64)
cap.set(cv2.CAP_PROP_CONTRAST, 0)
cap.set(cv2.CAP_PROP_EXPOSURE, 100) #in Linux l'esposizione e' 1/n
#cap.set(cv2.CAP_PROP_AUTO_EXPOSURE, 1) #controlla autoesposizione 1=true

while(True):
    ret, frame = cap.read()
    cv2.imshow('frame', frame)
    if cv2.waitKey(1) & 0xFF == ord('q'):
         break

cap.release()
cv2.destroyAllWindows()

Aruco tag e image stacking

Per cercare di migliorare il riconoscimento dei tag (Aruco, April) ho provato a fare image stacking per ridurre il rumore

Ho trovato una soluzione gia' pronta in questo repository

https://github.com/maitek/image_stacking

La definizione dei bordi e' nettamente migliorata

Image stacking

 
Immagine singola

giovedì 17 agosto 2023

Subpixel corner detection con Opencv

Ho provato a vedere se la funzione di  corner detection poteva essere utile con gli aruco tags per vedere se vi erano spostamenti


Non sembra ma quella sopra e' una gif animata (187 frames)

Ho preso l'esempio della libreria OpenCV applicato ad una serie di immagini di un Aruco tag statico ripreso con differenti condizioni di illuminazione

from __future__ import print_function
import cv2 as cv
import numpy as np
import argparse
import random as rng

source_window = 'Image'
maxTrackbar = 25
rng.seed(12345)

def goodFeaturesToTrack_Demo(val):
maxCorners = max(val, 1)
# Parameters for Shi-Tomasi algorithm
qualityLevel = 0.01
minDistance = 10
blockSize = 3
gradientSize = 3
useHarrisDetector = False
k = 0.04
# Copy the source image
copy = np.copy(src)
# Apply corner detection
corners = cv.goodFeaturesToTrack(src_gray, maxCorners, qualityLevel, minDistance, None, blockSize=blockSize, gradientSize=gradientSize, useHarrisDetector=useHarrisDetector, k=k)
# Draw corners detected
radius = 4
winSize = (5, 5)
zeroZone = (-1, -1)
criteria = (cv.TERM_CRITERIA_EPS + cv.TermCriteria_COUNT, 40, 0.001)
# Calculate the refined corner locations
corners = cv.cornerSubPix(src_gray, corners, winSize, zeroZone, criteria)
# Write them down
for i in range(corners.shape[0]):
print(corners[i,0,0],";",corners[i,0,1],";",end='')
parser = argparse.ArgumentParser(description='Code for Shi-Tomasi corner detector tutorial.')
parser.add_argument('--input', help='Path to input image.', default='pic3.png')
args = parser.parse_args()
src = cv.imread(cv.samples.findFile(args.input))
if src is None:
print('Could not open or find the image:', args.input)
exit(0)
src_gray = cv.cvtColor(src, cv.COLOR_BGR2GRAY)
maxCorners = 10 # initial threshold
goodFeaturesToTrack_Demo(maxCorners)
print()


il risultato dell'algotitmo e' di questo tipo




Alla fine e' emerso l'algoritmo seleziona in modo automatico gli angoli con un ordine diverso  e c'e' una forte influenza dell'illuminazione sulla definizione delle coordinate dell'angolo 

In casi ottimali (ma non e' questo il caso) ho visto che la standard deviation e' di circa 0.2 pixels

Non e' quindi un metodo affidabile per il change detection








sabato 22 luglio 2023

Aruco vs Apriltag condizioni reali

Aggiornamento:

Dopo aver provato un po' di tutto per correggere i dati ho scoperto che le immagini originali non sono state riprese in modo corretto. La camera satura in alcuni condizioni di luce come si vede dai due esempi sottostanti rendendo inutile l'elaborazione





Prova comparativa per misuare distanze tramite April ed Aruco tag in condizioni reali 

Lo scopo e' quello di verificare la ripetibilita' delle misure di distanza mediante tag a condizioni di luce variabile e per questo sono state effettuate misure con i tag in posizione stazionaria

E' stata impiegata una camera di sorveglianza a fuoco fisso con acquisizione ogni 10 minuti anche di notte grazie all'illuminazione ad infrarossi

I tag sono stati di dimensione di 25 cm in formato 4x4 per Aruco e 36h11 per gli Apriltag


Per determinare la distanza sono state impiegate le librerie OpenCV su Python per agli Aruco Tags mentre la libreria Apriltags3 in C++



in generale gli Apriltag risultano meglio individuabili rispetto agli Aruco tag. Di 430 immagini totali gli Apriltags sono stati individuati al 99% ad una distanza di 6 m mentre gli Aruco tag hanno prestazioni simili ma solo sulla breve distanza (4 m)


Aruco

l'elaborazione dei tag aruco indica che l'algoritmo genera molti outliers che possono essere facilmente separati dai dati corretti












Provando a smussare i dati con una media mobile a finetra oraria la situazione non migliora e si osserva un comportamento legato all'illuminazione che si ritrovera' anche dopo con gli April Tags 



Apriltag

Dall'analisi dei grafici si vede che le condizioni di illuminazione condizionano fortemente la misura della distanza mediante Apriltag. Le misure piu' stabili sono di notte quando e' attiva l'illuminazione dei led ad infrarossi

Rispetto ad Aruco ci sono molti meno outliers ed i dati sono meno rumorosi



In queste condizioni e' stato registrato un errore di standard devitation pari a 0.96% della distanza per il tag a 6.5m e dell'1% per il tag a 10 m

Se si plottano i dati a parita' di ora del giorno si vede ancora piu' chiaramente come la presenza di ombra influenza il dato di distanza

Se si fa la differenza tra le due curve l'errore scende al 0.18%


Un sistema per rimuovere l'effetto dell'illuminazione e' di correlare i dati dei due tag (sono vicini quindi sono illuminati in modo comparabile)


Per cercare di risolvere il problema delle differenti illuminazione ho provato ad elaborare le immagini mediante l'algoritmo presentato in questo articolo (Illumination Invariant Imaging: Applications in Robust Vision-based Localisation, Mapping and Classification for Autonomous Vehicles)

In estrema sintesi l'algoritmo appiattisce una immagine RGB e cerca di annullare gli effetti di differente illuminaizone (questo algoritmo funziona solo sulle immagini diurne perche' la camera di notte acquisisce a scala di grigi)


Per le elaborazioni ho usato questo progetto su Github.  (ho dovuto fare una leggere modifica perche' le immagini della camera avevano dei valori zero che ovviamente non potevano essere usati in un logaritmo)

import cv2
import numpy as np
import os
from argparse import ArgumentParser
from multiprocessing import Pool

ROBOTCAR_ALPHA = 0.4642


def transform(im):
# Assumes image is RGB-ordered
image = cv2.imread(read_dir + "/" + im, cv2.IMREAD_UNCHANGED)
r, g, b = image[:, :, 0], image[:, :, 1], image[:, :, 2]


// per eliminare gli eventuali zeri dalla matrice
r = np.where(r==0, 0.1, r)
g = np.where(g==0, 0.1, g)
b = np.where(b==0, 0.1, b)
ii_image = 0.5 + np.log(g) - ROBOTCAR_ALPHA*np.log(b) - (1-ROBOTCAR_ALPHA)*np.log(r)

# Lastly, convert from float to uint8 space
max_ii = np.max(ii_image)
min_ii = np.min(ii_image)
uii = np.uint8((ii_image - min_ii) * 256 / (max_ii - min_ii))
ii_name = write_dir + "/" + im
cv2.imwrite(ii_name, uii)
return ii_image


def transform_loop(directory):
image_names = os.listdir(directory)

# Spawn 4 worker processes to transform in parallel
p = Pool(4)
p.map(transform, image_names)


if __name__ == '__main__':
parser = ArgumentParser(
description=
'Transform images in a directory into lighting invariant color space')
parser.add_argument('--read-dir', action="store", type=str, required=True)
parser.add_argument('--write-dir', action="store", type=str, required=True)
args = parser.parse_args()

global read_dir, write_dir
read_dir = args.read_dir
write_dir = args.write_dir
transform_loop(read_dir)



dopo l'applicazione della elaborazione l'algoritmo di riconoscimento dei tag risulta molto piu' in difficolta' nel riconoscere i taf e sono state estratte solo 71 misure di distanza del tag1 e 16 misure del tag 2









Aggiornamento:

Frugando dentro al codice della demo di Apritag3 c'e' un porzione di codice che non puo' mai essere eseguito (c'e' un IF sempre True) e la condizione Falsa e' appunto l'algoritmo di Illumination Invariant 

Basta modificare la riga 156 per esempio aggiungendo un NOT si introduce il calcolo


questi sono i grafici risultanti dopo l'algoritmo. Si e' oersa la ritmicita' dell'illuminazione ma si e' persa anche la capacita' di riconoscere i tag nelle immagini trattate (per il tag 1 circa il 50%, tag2 decisamente peggio)



L'errore percentuale delle standard deviation e' pari a 1.79% per il tag1 e 1.08% per il tag 2
La differenza risiede nel valore del parametro utilizzato nell'elaborazione delle immagini




martedì 27 giugno 2023

Confronto ArucoTags ed AprilTags

 Ho provato una comparazione sulla stabilita' della misura di distanza usando Aruco Tags (con Opencv) e AprilTags (con AprilTag v2 e v3 modificata per avere la pose estimation)

Sono state acquisite 576 immagini con una camera Reolink E1 Pro (con immagini sia notturne che diurne) di due tag delle medesime dimensioni (4x4_100 per Aruco e 36h11 per Apriltag 10 cm)

Le nuove versioni di OpenCV hanno modificate le API per cui gli script che ho usato l'anno scorso non funzionano piu'. Per fare prima ho usato il docker sottostante che monta OpenCV 4.5.5

docker run -it -v /home/luca/ottico/:/home/ottichdgigante/python-opencv:4.5.5-debian  sh




OpenCV ed Aruco mostrano outliers, Apriltag3 ne mostra solo uno mentre Apriltag 2 ha mostrato dati molto piu' stabili)




Rimossi gli outlier si evidenzia che l'errore quadratico medio di Arpriltag e' circa la meta' di quella di Aruco (l'errore quadratico  migliore e' circa lo 0,3 % della distanza)






mercoledì 14 giugno 2023

Aruco calibration file format

 Ho provato a vedere se effettuare la calibrazione di tag Aruco con immagini in formato raw lossless od in formato jpg (lossy) poteva influenzare l'errore finale




Per fare cio' ho preso un telefono Android che permette di salvare la stessa immagine in formato DNG ed in formato JPG 

Lo script di calcolo e' il seguente

import numpy as np
import cv2
import cv2.aruco as aruco

# Aruco parameters
aruco_dict = aruco.Dictionary_get(aruco.DICT_4X4_50)
aruco_params = aruco.DetectorParameters_create()

# Chessboard parameters
num_chessboard_corners_horizontal = 9
num_chessboard_corners_vertical = 6
chessboard_square_size = 0.025  # in meters

# Image directory and file extension
image_directory = 'path_to_images_directory/'
image_extension = '.jpg'

# Arrays to store object points and image points
obj_points = []  # 3D points in real-world coordinates
img_points = []  # 2D points in image plane

# Generate chessboard object points
chessboard_size = (num_chessboard_corners_horizontal,
num_chessboard_corners_vertical)
objp = np.zeros((num_chessboard_corners_horizontal *
num_chessboard_corners_vertical, 3), np.float32)
objp[:, :2] = np.mgrid[0:num_chessboard_corners_horizontal,
0:num_chessboard_corners_vertical].T.reshape(-1, 2)
objp = objp * chessboard_square_size

# Iterate through images and find chessboard corners
image_count = 0
while True:
    # Load image
    image_path = image_directory + str(image_count) + image_extension
    image = cv2.imread(image_path)
    if image is None:
        break

    # Convert to grayscale
    gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

    # Find chessboard corners
    ret, corners = cv2.findChessboardCorners(gray, chessboard_size, None)

    # If corners found, add object points and image points
    if ret == True:
        obj_points.append(objp)
        img_points.append(corners)

        # Draw and display corners
        cv2.drawChessboardCorners(image, chessboard_size, corners, ret)
        cv2.imshow('Chessboard Corners', image)
        cv2.waitKey(500)

    image_count += 1

cv2.destroyAllWindows()

# Perform Aruco calibration
ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(obj_points,
img_points, gray.shape[::-1], None, None)

# Calculate reprojection error
mean_error = 0
for i in range(len(obj_points)):
    img_points_proj, _ = cv2.projectPoints(obj_points[i], rvecs[i],
tvecs[i], mtx, dist)
    error = cv2.norm(img_points[i], img_points_proj, cv2.NORM_L2) /
len(img_points_proj)
    mean_error += error

calibration_error = mean_error / len(obj_points)
print("Calibration error: {}".format(calibration_error))

# Estimate additional error
reproj_error = 0
total_points = 0
for i in range(len(obj_points)):
    img_points_proj, _ = cv2.projectPoints(obj_points[i], rvecs[i],
tvecs[i], mtx, dist)
    error = cv2.norm(img_points[i], img_points_proj, cv2.NORM_L2)
    reproj_error += error
    total_points += len(obj_points[i])

additional_error = reproj_error / total_points
print("Additional error: {}".format(additional_error))

I risultati sono i seguenti (per i file DNG ho usato la libreria rawpy)

JPG

python calibration.py --dir ./jpg/ --square_size 0.015  -w 9 -t 6
Calibration error: 0.13602731796497697
Additional error: 0.13602731796497697

DNG

Calibration error: 0.13606447706803765

Additional error: 0.13606447706803765

In conclusione non si ha una significativa differenza di errore nell'usare JPG o DNG

giovedì 1 giugno 2023

Match Histogram

Un esempio per normalizzare l'istogramma tra due immagini riprese a differente orari con ovvie differenze di illuminazione mediante la funzione match_histogram di Scikit (attenzione che nelle versioni precedenti la funzione era definita in modo differente per i parametri)


Immagine corretta. Si usa la prima come reference e la seconda come immagine da correggere


 Questi gli istogrammi prima e dopo l'algoritmo


from skimage import exposure
import matplotlib.pyplot as plt
import argparse
import cv2
ap = argparse.ArgumentParser()
ap.add_argument("-s", "--source", required=True,
help="path to the input source image")
ap.add_argument("-r", "--reference", required=True,
help="path to the input reference image")
args = vars(ap.parse_args())

print("[INFO] loading source and reference images...")
src = cv2.imread(args["source"])
ref = cv2.imread(args["reference"])
print("[INFO] performing histogram matching...")
multi = True if src.shape[-1] > 1 else False
matched = exposure.match_histograms(src, ref, channel_axis=-1)
cv2.imshow("Source", src)
cv2.imshow("Reference", ref)
cv2.imshow("Matched", matched)
cv2.imwrite("matched.jpg",matched)
cv2.waitKey(0)

(fig, axs) = plt.subplots(nrows=3, ncols=3, figsize=(8, 8))
for (i, image) in enumerate((src, ref, matched)):
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
for (j, color) in enumerate(("red", "green", "blue")):
(hist, bins) = exposure.histogram(image[..., j],
source_range="dtype")
axs[j, i].plot(bins, hist / hist.max())
(cdf, bins) = exposure.cumulative_distribution(image[..., j])
axs[j, i].plot(bins, cdf)
axs[j, 0].set_ylabel(color)

axs[0, 0].set_title("Source")
axs[0, 1].set_title("Reference")
axs[0, 2].set_title("Matched")
plt.tight_layout()
plt.savefig("grafico.png")

 

mercoledì 12 aprile 2023

Trasformazione affine con OpenCV

 Alla fine degli anni 80 se non erro alla Domenica Sportiva c'era un  primordiale sistema di computer grafica che consentiva di valutare il fuorigioco in una partita di calcio da immagini televisive....mi ero sempre chiesto come era possivile....dopo tanto tempo ho visto che e' possibile tramite la trasformazione affine 

 Partiamo da una immagine generica dello stadio di Firenze


 Le dimensione del campo dell'Artemio Franchi sono 105x68 m mentre nell'immagine sottostante sono riportate le dimensioni ufficiali del campo da calcio


 La trasformazione affine serve a trasformare le coordinate immagine in un altro sistema di coordinate (in questo caso coordinate centrimetriche reali con origine degli assi nella bandierina del calcio d'angolo in basso a sinistra)
OpenCV richiede tre punti ...per esempio la bandierina del calcio d'angolo in alto a sinistra ha coordinate pixel di 362x208 mentre coordinate reali 10500,6800 cm. I punti sono stati selezionati sull'incrocio di alcune linee dell'area di rigore
Nell'immagine successiva il risultato dell'algoritmo...non perfetto perche' le linee dovrebbero essere ortogonali ma considerando le incertezze direi che non e'male

 
import matplotlib.pyplot as plt
import numpy as np
import cv2 as cv

img = cv.imread('fiorentina.jpg')
pts1 = np.float32([[193,132],[362,208],[542,214]])
pts2 = np.float32([[10500,6800],[9950,4315],[10500,3765]])

M = cv.getAffineTransform(pts1,pts2)
dst = cv.warpAffine(img,M,(10500,6800))

plt.imshow(dst)
plt.show()
plt.savefig("dest.png")
plt.close(dst)


 

martedì 1 novembre 2022

Influenza dell'angolo di ripresa su Aruco Tags

 Usando gli Aruco Tags mi sono accorto che se vario la posizione di ripresa mantenendo i tag stabili gli angoli e le distanze fornite da RVec e TVec cambiano sensibilmente

Su github ho trovato questa libreria https://github.com/tentone/aruco in cui e' contenuta una tabella riassuntiva in cui si vede chiaramente l'effetto dell'angolo di ripresa sulla stima della distanza con errori anche sensibili per modeste (inferiori ai 10 gradi) variazioni dell'angolo di ripresa



in questo video si osserva che la distanza relativa tra i tag varia da un minimo di 31 ad un massimo di 40 cm con angoli tra 70 ed 80 al variare del punto di ripresa

sabato 29 ottobre 2022

Distanza ed angoli relativi tra due tag Aruco

 La distanza reale centro centro tra i due tags e' di 35 cm



'''
Sample Command:-
python detect_aruco_video.py --type DICT_5X5_100 --camera True
python detect_aruco_video.py --type DICT_5X5_100 --camera False --video test_video.mp4 -a 25 -k ./calibration_matrix.npy -d ./distortion_coefficients.npy
'''

from turtle import delay
import numpy as np
from utils import ARUCO_DICT, aruco_display
import argparse
import time
import cv2
import sys
import math
import time

def isRotationMatrix(R):
Rt = np.transpose(R)
shouldBeIdentity = np.dot(Rt, R)
I = np.identity(3, dtype=R.dtype)
n = np.linalg.norm(I - shouldBeIdentity)
return n < 1e-6

def rotationMatrixToEulerAngles(R):
assert (isRotationMatrix(R))

sy = math.sqrt(R[0, 0] * R[0, 0] + R[1, 0] * R[1, 0])

singular = sy < 1e-6

if not singular:
x = math.atan2(R[2, 1], R[2, 2])
y = math.atan2(-R[2, 0], sy)
z = math.atan2(R[1, 0], R[0, 0])
else:
x = math.atan2(-R[1, 2], R[1, 1])
y = math.atan2(-R[2, 0], sy)
z = 0

return np.array([x, y, z])

R_flip = np.zeros((3,3), dtype=np.float32)
R_flip[0,0] = 1.0
R_flip[1,1] =-1.0
R_flip[2,2] =-1.0

ap = argparse.ArgumentParser()
ap.add_argument("-i", "--camera", help="Set to True if using webcam")
ap.add_argument("-v", "--video", help="Path to the video file")
ap.add_argument("-t", "--type", type=str, default="DICT_ARUCO_ORIGINAL", help="Type of ArUCo tag to detect")
ap.add_argument("-k", "--K_Matrix", required=True, help="Path to calibration matrix (numpy file)")
ap.add_argument("-d", "--D_Coeff", required=True, help="Path to distortion coefficients (numpy file)")
ap.add_argument("-a", "--aruco_dim", required=True, help="ArUco tag dimension")
args = vars(ap.parse_args())


if args["camera"].lower() == "true":
video = cv2.VideoCapture(0)
time.sleep(2.0)
else:
if args["video"] is None:
print("[Error] Video file location is not provided")
sys.exit(1)

video = cv2.VideoCapture(args["video"])

if ARUCO_DICT.get(args["type"], None) is None:
print(f"ArUCo tag type '{args['type']}' is not supported")
sys.exit(0)

arucoDict = cv2.aruco.Dictionary_get(ARUCO_DICT[args["type"]])
calibration_matrix_path = args["K_Matrix"]
distortion_coefficients_path = args["D_Coeff"]
k = np.load(calibration_matrix_path)
d = np.load(distortion_coefficients_path)
arucoParams = cv2.aruco.DetectorParameters_create()

while True:
ret, frame = video.read()
time.sleep(0.05)
if ret is False:
break


h, w, _ = frame.shape

width=1000
height = int(width*(h/w))
frame = cv2.resize(frame, (width, height), interpolation=cv2.INTER_CUBIC)
corners, ids, rejected = cv2.aruco.detectMarkers(frame, arucoDict, parameters=arucoParams)

detected_markers = aruco_display(corners, ids, rejected, frame)
if len(corners) > 0:
#print(corners) #posizione degli angoli del marker
#print(len(ids))
if (len(ids) == 2):
for i in range(0, len(ids)):
rvec, tvec, markerPoints = cv2.aruco.estimatePoseSingleMarkers(corners[i], float(args["aruco_dim"]), k,d)
if (i == 0):
print("Vettore 1")
tvec0 = tvec
R_ct = np.matrix(cv2.Rodrigues(rvec)[0])
R_ct = R_ct.T
roll, pitch, yaw = rotationMatrixToEulerAngles(R_flip*R_ct)
A = ([math.cos(roll),math.cos(pitch),math.cos(yaw)])
print(tvec0)
print(A)
if (i==1):
print("Vettore 2")
tvec1 = tvec
#B = ([rvec[0][0][0],rvec[0][0][1],rvec[0][0][2]])
R_ct = np.matrix(cv2.Rodrigues(rvec)[0])
R_ct = R_ct.T
roll, pitch, yaw = rotationMatrixToEulerAngles(R_flip*R_ct)
B = ([math.cos(roll),math.cos(pitch),math.cos(yaw)])
print(tvec1)
print(B)

# primo metodo per il calcolo della distanza
tvec0_x = tvec0[0][0][0]
tvec0_y = tvec0[0][0][1]
tvec0_z = tvec0[0][0][2]
tvec1_x = tvec1[0][0][0]
tvec1_y = tvec1[0][0][1]
tvec1_z = tvec1[0][0][2]
dist1 = math.sqrt(pow((tvec0_x-tvec1_x),2)+pow((tvec0_y-tvec1_y),2)+pow((tvec0_z-tvec1_z),2))
distanza1= "Dist=%4.0f"%(dist1)
#secondo metodo per il calcolo della distanza
#
distanza= "Dist=%4.0f"%(np.linalg.norm(tvec1-tvec0))
cv2.putText(frame, distanza1,(50, 100),cv2.FONT_HERSHEY_SIMPLEX, 1,(0, 0, 255), 2, cv2.LINE_4)
dot_product = np.dot(A,B,out=None)
normA = (np.linalg.norm(A))
normB = (np.linalg.norm(B))
cos_angolo = dot_product/(normA*normB)
angolo_rad = np.arccos(cos_angolo)
angolo_deg = np.rad2deg(angolo_rad)
if (angolo_deg > 90):
angolo_deg = 180 - angolo_deg
ang = "Ang=%4.1f"%(angolo_deg)
cv2.putText(frame,ang ,(50, 150),cv2.FONT_HERSHEY_SIMPLEX, 1,(0, 0, 255), 2, cv2.LINE_4)


cv2.imshow("Image", detected_markers)
key = cv2.waitKey(1) & 0xFF
if key == ord("q"):
break

cv2.destroyAllWindows()
video.release()

Geologi

  E so anche espatriare senza praticamente toccare strada asfaltata