Accéléré en interne avec le service de surveillance en nuage IPEYE

Récemment, une tâche est apparue quotidiennement pour former un time-lapse avec une paire de caméras de surveillance connectées à IPEYE. Si vous êtes intéressé par la façon dont une personne avec une connaissance minimale de python a géré cela, ou si vous voulez me signaler mes erreurs, bienvenue à…

Intro


Mon père a décidé de déménager et de construire une maison dans une autre région. Il m'a demandé d'aider à la vidéosurveillance. Données d'entrée:

  • Il n'y a pas de local technique.
  • L'Ă©quipement peut ĂŞtre volĂ©.
  • Besoin d'une image de qualitĂ©.
  • Les camĂ©ras doivent ĂŞtre extĂ©rieures.
  • Tout ce dont vous avez besoin est de 2 camĂ©ras.
  • Je veux vraiment des camĂ©ras PTZ avec zoom.
  • J'aimerais une application mobile.

Après avoir regardé les prix dans les magasins pour les équipements de marque, il a été décidé d'acheter des caméras PoE non nominatives avec tous les petits pains d'Ali. Les caméras étaient assez bon marché - environ 5 000 chacune.

Je ne voulais pas placer le DVR sur un chantier de construction, il a donc été décidé d'utiliser une solution cloud. Après l'arrivée des caméras, j'ai essayé de me faire des amis avec différents services pour que tout fonctionne, y compris PTZ. De tous les services que j'ai essayés, il s'est avéré ne faire des amis des caméras chinoises qu'avec IPEYE.
Cela mettra fin Ă  l'intro. Je pense que maintenant, tout le monde comprendra pourquoi la conversation portera sur ce service.

Expérience avec IPEYE


Service en tant que service. Tout ce qui a été promis est rempli. Le support technique répond aux questions. Le point clé est que vous pouvez spécifier un lien vers le flux rstp, tordre les paramètres PTZ et tout fonctionnera. L'application mobile Android fonctionne. Il est possible de créer des utilisateurs invités pour leurs caméras et de donner des droits d'accès à chaque parent. L'interface Web de Vivaldi est parfois boguée; dans Chrome, ces problèmes sont moins courants. Un peu ennuyeux en parcourant l'archive.
L'archive de la caméra peut être téléchargée, mais jusqu'à 3 heures. La procédure prend beaucoup de temps.

Tout semble aller bien, mais vous sentez avec votre âme que quelque chose ne va pas. Et il n'y a pas assez de profondeur d'archivage. Vous pouvez augmenter la profondeur, mais l'option la plus chère - 12 mois coûtera 25 mille roubles par an pour une caméra (lors de l'enregistrement par détection).

Trouvons quelque chose?


C'est la question que mon père m'a posée. Mon père voulait saisir toutes les étapes de la construction.
Quelles sont les options pour résoudre ce problème? Vous pouvez accéder quotidiennement à l'interface Web et exporter plusieurs séquences vidéo de chaque caméra. Qui exécutera une tâche aussi morne? Personne! Plusieurs fois par jour pour ouvrir des caméras de diffusion et prendre des captures d'écran? Eh bien, aussi la folie. Augmenter la profondeur des archives à 1 an? Eh bien, une solution très bon marché. Google m'a dit que la fonction TimeLapse est déjà intégrée à ce service, mais la résolution est faible et vous ne pouvez pas la télécharger pour les futures archives :(
UPD: timelapse original peut être téléchargé. Pour ce faire, ouvrez le laps de temps dans l'interface Web. À l'aide des outils de développement, inspectez la page Web et trouvez un lien vers notre fichier. Format de lien:
sr <numéro_serveur> .ipeye.ru / api / v1 / stream / <uuid> / nvr / timelapse / 0 / <unixtimestamp> /100/video.mjpeg

Il a été décidé d'écrire quelque chose pour sauvegarder les captures d'écran de la caméra et former la vidéo finale.

Clause de non-responsabilité


L'auteur de cet opus n'est pas programmeur et ne cherche pas à le devenir. La POO connaît les bases superficiellement. Agile, etc. pas étudié. Il y a un mois, il a regardé un court cours vidéo sur le python et a décidé de l'utiliser pour résoudre le problème actuel.

API


Il était très agréable de constater que le service IPEYE est une API accessible au public. L'API fournit des exemples pour PHP uniquement, mais elle s'est avérée utile.

Basé sur le fait que mon ordinateur ne s'arrête jamais, le concept suivant a été développé:

  1. Le planificateur Windows exécute un script toutes les 30 minutes.
  2. Le script via l'API détermine l'uuid de mes caméras.
  3. Le script via l'API reçoit la photo des caméras et l'enregistre dans un répertoire.
  4. Le script génère une fois par jour un fichier vidéo pour chaque caméra.
  5. Le catalogue avec les photos sources et les vidéos finales est lié au cloud et partagé.
  6. Parents quand ils veulent regarder le timelapse et opérer sur les fichiers comme ils le souhaitent.

Pour travailler avec l'API, j'ai écrit quelques fonctions: journalisation et exécution des requêtes sur le serveur API.

writeLog ()
def writeLog(logdata): if LogEnable == 1: log_time = datetime.now() log_time = log_time.isoformat(timespec='seconds') log_file = open(log_file_path, "a+") log_file.write(log_time + ": " + str(logdata) + "\n") log_file.close else: return True 


getApiResponse ()
 def getApiResponse(method, api_uri): if method == "GET": try: r = requests.get(api_url + api_uri, timeout = api_timeout) r.raise_for_status() #   HTTP    except requests.exceptions.Timeout: writeLog("Error. Timeout. Request Uri:" + api_uri) except requests.exceptions.TooManyRedirects: writeLog("Error. TooManyRedirects or bad URL. Request Uri:" + api_uri) except requests.exceptions.RequestException as e: writeLog("Error. Fatal error: " + str(e) + " Request Uri:" + api_uri) sys.exit(1) except requests.exceptions.HTTPError as e: writeLog("Error. HTTP error: " + str(e) + " Request Uri:" + api_uri) if method == "POST": try: r = requests.post(api_url + api_uri, timeout = api_timeout) r.raise_for_status() #   HTTP    except requests.exceptions.Timeout: writeLog("Error. Timeout. Request Uri:" + api_uri) except requests.exceptions.TooManyRedirects: writeLog("Error. TooManyRedirects or bad URL. Request Uri:" + api_uri) except requests.exceptions.RequestException as e: writeLog("Error. Fatal error: " + str(e) + " Request Uri:" + api_uri) sys.exit(1) except requests.exceptions.HTTPError as e: writeLog("Error. HTTP error: " + str(e) + " Request Uri:" + api_uri) return r 


Selon l'API, nous avons la possibilité d'accéder à / devices / all et d'obtenir des informations sur tous les threads. J'ai immédiatement été confus que l'autorisation n'est pas requise, si vous croyez la documentation de l'API ... Quand j'ai demandé / devices / all, j'ai eu une erreur:
Erreur fatale: 401 Erreur client: non autorisé pour l'URL: api.ipeye.ru : 8111 / devices / all
J'ai modifié ma fonction getApiResponse pour transmettre mes informations d'identification, mais j'ai également reçu une erreur 401. Je ne répertorie pas la fonction, car ce n'était pas utile à l'avenir.

J'ai dû contacter le support technique à ce sujet. L'équipe de support a expliqué que pour utiliser l'API avec autorisation, vous devez d'abord conclure un accord avec IPEYE et configurer votre serveur Web. Quel type de serveur Web et pourquoi ils ne m'ont pas expliqué, mais en même temps, ils ont donné un indice où obtenir des caméras uuid et accéder à l'API sans autorisation.

Correspondance avec support
Moi: Bonjour. Est-il possible d'utiliser l'API? Je n'ai pas trouvé de caméras uuid dans l'interface Web. Je voulais obtenir une liste de mes caméras lorsque j'ai demandé à api.ipeye.ru : 8111 / devices / all mais mon lien nom d'utilisateur / mot de passe ne fonctionnait pas.

IPEYE: En tant que login / mot de passe, les données d'autorisation de l'utilisateur de l'API sont utilisées, nous fournissons un accès sur une base contractuelle.

En conséquence, la liste des caméras que vous verrez ne sera pas pour votre connexion, mais pour toutes les caméras de l'API utilisateur.

Moi: combien cela coûte-t-il d'avoir accès à l'API de deux caméras?

IPEYE: Tout y est plus compliqué, vous devrez lancer votre site et utiliser notre API pour y ajouter des caméras et continuer à travailler avec elles.
Est-ce que ça vaut le coup de le faire pour 2 caméras?

Moi: En général, pour comprendre, j'ai besoin d'accéder à l'API uniquement pour télécharger automatiquement des captures d'écran des caméras. Pour la formation ultérieure de laps de temps.

IPEYE: Et qu'est-ce que api.ipeye.ru/doc#AppDeviceJPEGOnline ne convient pas dans ce cas?

Moi: Et où puis-je trouver les UUID de mes caméras?

IPEYE: depuis la barre d'adresse du navigateur, par exemple. Le même UUID apparaît dans le "Code du site" comme paramètre devcode.



Qu'avons-nous dans le résidu sec? En effet, des caméras uuid peuvent être trouvées dans votre compte, si vous regardez les paramètres GET. Il y a une méthode / device / jpeg / online /: uuid /: nom pour prendre une capture d'écran, il y a uuid, et nous connaissions déjà le nom de la caméra.

Je crée une fonction pour enregistrer les images d'un flux.

saveJpegFromStream ()
 def saveJpegFromStream(uuid, name): # api_uri = "/device/thumb/online/" + uuid + "/1920/" + name api_uri = "/device/jpeg/online/" + uuid + "/" + name writeLog("Trying save Stream screenshot for camera: " + name) response = getApiResponse("GET", api_uri) content_type = response.headers.get('content-type') if content_type is None: writeLog("Nothing to save") return False if 'text' in content_type.lower() or 'html' in content_type.lower(): writeLog("Received text data: " + response.content.decode("utf-8")) return False else: filename = dirToSave + "\\" + name + "-" + today.strftime("%Y-%m-%d-%H-%M-%S") + ".jpg" screenshot = open(filename, "wb") screenshot.write(response.content) screenshot.close() writeLog("File saved as: " + filename) return True 


Je fais une demande et je comprends que quelque chose ne va pas ... Nous avons reçu le dossier. Image de mon appareil photo, mais la taille est étrangement petite - 66 Ko. Je regarde les propriétés et je comprends que 608 * 342 n'est en aucun cas 1920 * 1080. Lorsque je demande / device / thumb / online / uuid / 1920 / name, j'obtiens le fichier 1920 * 1080, mais ce n'est que le fichier précédent 608 * 342 étiré à l'échelle requise. Bien sûr, cet état de fait ne me convenait pas.

Également au cours des expériences, il est découvert que le paramètre de nom n'est ni vérifié ni utilisé de quelque façon que ce soit. Vous pouvez envoyer ce que vous voulez.

Après cela, j'ai conclu que la commande / device / url / rtsp / la plus utile est d'obtenir un lien vers le flux RTSP. J'ai dû me tourner vers Google, car On ne comprenait pas comment travailler avec RTSP.

getStreamRTSP ()
 def getStreamRTSP(uuid, name): api_uri = "/device/url/rtsp/" + uuid writeLog("Trying get stream RTSP link for " + name + " " + uuid) response = json.loads(getApiResponse("GET", api_uri).text) writeLog("Stream RTSP link for " + name + ": " + str(response["message"])) return str(response["message"]) 


saveJpegFromRTSP
 def saveJpegFromRTSP(name, rtspLink): writeLog("Trying save RTSP screenshot for camera: " + name) rtspClient = cv2.VideoCapture(rtspLink) if rtspClient.isOpened(): _,frame = rtspClient.read() rtspClient.release() #       if _ and frame is not None: filename = dirToSave + "\\" + name + "-" + today.strftime("%Y-%m-%d-%H-%M-%S") + ".jpg" cv2.imwrite(filename, frame) writeLog("File saved as: " + filename) return True else: writeLog("Can't read RTSP stream") return False 


Après avoir écrit quelques fonctions ci-dessus, je me suis rendu compte qu'il n'était pas nécessaire d'utiliser l'API pour la tâche initiale :) J'ai un lien direct vers le flux rtsp de la caméra, et avec la dernière fonction, je peux capturer l'image de la caméra. Mais je n'ai pas commencé à changer le concept à ce stade.

Lorsque vous travaillez via l'API, il y a 2 avantages: le serveur IPEYE initialise le flux beaucoup plus rapidement que la caméra, et le flux de la caméra rtsp peut être fermé par un pare-feu.

jpg2mp4


La dernière étape consiste à ajouter toutes les images d'une caméra à la vidéo. J'ai choisi le codec mp4v, car MEGA vous permet de lire ces fichiers vidéo dans une interface Web.

makeVideoFile ()
 def makeVideoFile(name): height = 1080 width = 1920 # video = cv2.VideoWriter(dirToSave + "\\Video" + name + ".avi", cv2.VideoWriter_fourcc(*'DIVX'), 1,(width,height)) video = cv2.VideoWriter(dirToSave + "\\Video" + name + ".mp4", cv2.VideoWriter_fourcc(*'mp4v'), 1,(width,height)) files = os.listdir(dirToSave) screenshots = list(filter(lambda x: x.startswith(name + "-"), files)) for screenshot in screenshots: origImage = cv2.imread(dirToSave + "\\" + screenshot) #  ,    ,      -   ...   #  ,        FullHD ?           API heightOrig, widthOrig, channelsOrig = origImage.shape if height != heightOrig or width != widthOrig: img = cv2.resize(origImage, (width, height)) video.write(img) else: video.write(origImage) cv2.destroyAllWindows() video.release() 


Pour référence: 45 fichiers jpg avec un volume total de 37,3 Mo au format vidéo occupent 16,9 Mo.

Merci d'avoir lu mon premier article public. J'ai essayé de tout décrire dans le format de l'histoire, et pas simple à faire, car je ne voulais pas avoir un article sec en sortant.

Je serai ravi de faire des commentaires, car j'ai rencontré python il y a moins d'un mois.

Le script complet avec des commentaires étendus peut être trouvé sur github . De plus, le script prend en compte la différence de fuseaux horaires et toutes les fonctions restantes sont écrites pour s'adapter rapidement à un nombre différent de caméras.

Source: https://habr.com/ru/post/fr475154/


All Articles