[PYTHON] Détecter le retour du chat avec OpenCV

introduction

Si vous vous concentrez sur votre travail de bureau, vous risquez de vous retrouver avec un chat sans vous en rendre compte. C'est encore plus vrai avec le travail à distance car il n'y a pas les yeux des autres.

J'ai donc créé un mécanisme pour vous alerter lorsque votre posture se détériore!

environnement

Python 3.7.4 Caméra Web (Logitech HD Webcam C615)

OpenCV

Installation

$ pip install opencv-python

Tout d'abord, nous capturerons l'image de la caméra et détecterons les yeux.

Ce qui suit est une référence pour la méthode de détection d'objets dans OpenCV.

"Détection de visage avec Haar Cascades" http://labs.eecs.tottori-u.ac.jp/sd/Member/oyamada/OpenCV/html/py_tutorials/py_objdetect/py_face_detection/py_face_detection.html#face-detection

Puisque nous voulons détecter les yeux cette fois, nous utiliserons "haarcascade_eye.xml" comme classificateur. Vous pouvez le télécharger à partir du lien ci-dessous.

https://github.com/opencv/opencv/tree/master/data/haarcascades

capture.py


import numpy as np
import cv2

#Numéro de l'appareil photo
#Les caméras disponibles sont commandées à partir de 0
DEVICE_ID = 0

#Sélectionnez le classificateur
cascade = cv2.CascadeClassifier('haarcascade_eye.xml')

#Capturez des images de la caméra
cap = cv2.VideoCapture(DEVICE_ID, cv2.CAP_DSHOW)

while cap.isOpened():
    #Obtenir le cadre
    ret, frame = cap.read()

    #Convertir en échelle de gris
    img_gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)

    #Détecter l'œil
    eyes = cascade.detectMultiScale(img_gray, minSize=(30, 30))

    #Entourez l'œil détecté dans un carré
    for (x, y, w, h) in eyes:
        color = (255, 0, 0)
        cv2.rectangle(img_gray, (x, y), (x+w, y+h), color, thickness=3)

    #afficher
    cv2.imshow('capture', img_gray)

    #Quittez la boucle avec la touche ESC
    if cv2.waitKey(1) & 0xFF == 27:
        break

#Terminer le traitement
cv2.destroyAllWindows()
cap.release()

La ligne 25, «y», est l'information sur la hauteur des yeux détectée. Par conséquent, ce n'est pas grave si vous enregistrez cette valeur et voyez comment elle tombe!

moyenne mobile

Eh bien, j'ai la hauteur des yeux, mais ce n'est pas si bon d'utiliser cette valeur telle quelle. Ce n'est pas le cas qu'il est jugé comme le dos d'un chat juste en regardant vers le bas pendant un moment, donc je vais gérer la valeur moyenne pendant une certaine période de temps à cette fin.

C'est là que la «moyenne mobile» entre en jeu. Ceux qui font des actions et des devises peuvent être familiers. Cette fois, j'ai utilisé la plus simple «moyenne mobile __» parmi les moyennes mobiles.

La moyenne mobile simple (SMA) est une moyenne simple non pondérée des n dernières données. Par exemple, une simple moyenne mobile des cours de clôture pendant 10 jours est la moyenne des cours de clôture des 10 derniers jours. Définissez leurs cours de clôture sur $ {\ displaystyle p_ {M}} p_ {{M}}, {\ displaystyle p_ {M-1}} p_ {{M-1}}, ..., {\ displaystyle p_ {M-9 }} p_ {{M-9}} $, alors la formule de la moyenne mobile simple SMA (p, 10) est:

{\text{SMA}}{M}={p{M}+p_{M-1}+\cdots +p_{M-9} \over 10}


 > Pour trouver la moyenne mobile simple du lendemain, ajoutez un nouveau cours de clôture et supprimez le cours de clôture le plus ancien. En d'autres termes, dans ce calcul, il n'est pas nécessaire de recalculer la somme.

>```math
{\text{SMA}}_{{\mathrm  {today}}}={\text{SMA}}_{{\mathrm  {yesterday}}}-{p_{{M-n+1}} \over n}+{p_{{M+1}} \over n}

Le fait est que vous pouvez faire la moyenne des n dernières parties de chaque élément des données.

Si la différence entre le début et la fin des données de la série chronologique de cette moyenne mobile simple est supérieure à un certain seuil, elle peut être considérée comme un retour de chat.

Conversion de projection

Les informations sur la hauteur des yeux «y» dont j'ai parlé jusqu'à présent ne sont que les informations de position des pixels de l'image de la caméra. Par conséquent, il est nécessaire de calculer séparément à combien de cm le changement de la valeur de «y» correspond dans le monde réel.

Par conséquent, je considérerai ce que l'on appelle la conversion par projection. Puisque l'image de la caméra est bidimensionnelle, il est nécessaire de projeter le monde tridimensionnel sur un plan bidimensionnel lors de sa visualisation. Cette fois, considérez-le comme une conversion de projection en perspective.

Les transformations de projection en perspective sont $ \ left (\ frac {x} {z}, \ frac {y} {z}, 0 \ right) $ pour les coordonnées tridimensionnelles $ (x, y, z) $. Convertir. Je pense que c'est facile à comprendre car cela correspond à l'intuition que "les choses éloignées semblent petites".

Par conséquent, la relation entre la différence de hauteur de l'image de la caméra $ \ Delta y_d $ (px) et la différence de hauteur du monde réel $ \ Delta y_v $ (cm) est la suivante.

\begin{equation}
  \frac{\Delta y_d}{f} = \frac{\Delta y_v}{z_v} \tag{1}
\end{equation}

$ z_v $ est la distance entre la caméra et l'objet, et $ f $ est la distance focale de la caméra. C'est facile compte tenu de la similitude des triangles.

diagram-20200929_2.png

La distance focale de la caméra variant en fonction du modèle de la caméra, il est nécessaire de l'obtenir par calibrage. L'étalonnage de la caméra est également fourni par OpenCV.

"Calibrage de la caméra" http://whitewell.sakura.ne.jp/OpenCV/py_tutorials/py_calib3d/py_calibration/py_calibration.html

Le contenu des paramètres internes de la caméra obtenus par calibrage est le suivant, et la distance focale peut être trouvée à partir d'ici.

K = \left[
      \begin{array}{ccc}
        f & 0 & x_c \\
        0 & f & y_c \\
        0 & 0 & 1
      \end{array}
    \right]

$ x_c, y_c $ sont les points centraux du plan de projection.
Cependant, ce calibrage est un peu gênant car je dois préparer une image de l'échiquier réellement prise avec la caméra. .. .. La caméra Web que j'utilise (Logitech HD Webcam C615) coûte environ 500 $ f $, donc pour référence.

Jugement de retour de chat

Une fois que vous obtenez $ f $, vous avez terminé.

Dans les données de séries chronologiques de la moyenne mobile simple au niveau des yeux, la différence entre le début et la fin est $ \ Delta y_d $, et il suffit de juger par l'équation (1). La distance $ z_v $ de l'appareil photo à l'ordinateur est d'environ 45 cm. Nous avons également défini le seuil de différence de hauteur des yeux dans le monde réel $ \ Delta y_v $ à 3 cm.

Code entier

Tracé avec pyplot pour la visualisation des données.

De plus, les alertes utilisent la boîte de message de Tkinter.

detect_posture.py


import numpy as np
from matplotlib import pyplot as plt
import cv2
import tkinter as tk
from tkinter import messagebox


WINDOW_NAME = "capture"     # Videcapute window name
CAP_FRAME_WIDTH = 640       # Videocapture width
CAP_FRAME_HEIGHT = 480      # Videocapture height
CAP_FRAME_FPS = 30          # Videocapture fps (depends on user camera)

DEVICE_ID = 0               # Web camera id

SMA_SEC = 10                        # SMA seconds
SMA_N = SMA_SEC * CAP_FRAME_FPS     # SMA n

PLOT_NUM = 20                   # Plot points number
PLOT_DELTA = 1/CAP_FRAME_FPS    # Step of X axis

Z = 45                  # (cm) Distance from PC to face 
D = 3                   # (cm) Limit of lowering eyes
F = 500                 # Focal length


def simple_moving_average(n, data):
    """ Return simple moving average """
    result = []
    for m in range(n-1, len(data)):
        total = sum([data[m-i] for i in range(n)])
        result.append(total/n)
    return result

def add_simple_moving_average(smas, n, data):
    """ Add simple moving average """
    total = sum([data[-1-i] for i in range(n)])
    smas.append(total/n)


if __name__ == '__main__':
    # Not show tkinter window
    root = tk.Tk()
    root.iconify()

    # Chose cascade
    cascade = cv2.CascadeClassifier("haarcascade_eye.xml")

    # Capture setup
    cap = cv2.VideoCapture(DEVICE_ID, cv2.CAP_DSHOW)
    cap.set(cv2.CAP_PROP_FRAME_WIDTH, CAP_FRAME_WIDTH)
    cap.set(cv2.CAP_PROP_FRAME_HEIGHT, CAP_FRAME_HEIGHT)
    cap.set(cv2.CAP_PROP_FPS, CAP_FRAME_FPS)

    # Prepare windows
    cv2.namedWindow(WINDOW_NAME)

    # Time series data of eye height
    eye_heights = []
    sma_eye_heights = []

    # Plot setup
    ax = plt.subplot()
    graph_x = np.arange(0, PLOT_NUM*PLOT_DELTA, PLOT_DELTA)
    eye_y = [0] * PLOT_NUM
    sma_eye_y = [0] * PLOT_NUM
    eye_lines, = ax.plot(graph_x, eye_y, label="realtime")
    sma_eye_lines, = ax.plot(graph_x, sma_eye_y, label="SMA")
    ax.legend()

    while cap.isOpened():
        # Get a frame
        ret, frame = cap.read()

        # Convert image to gray scale
        img_gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
        
        # Detect human eyes
        eyes = cascade.detectMultiScale(img_gray, minSize=(30, 30))

        # Mark on the detected eyes
        for (x, y, w, h) in eyes:
            color = (255, 0, 0)
            cv2.rectangle(img_gray, (x, y), (x+w, y+h), color, thickness=3)
        
        # Store eye heights
        if len(eyes) > 0:
            eye_average_height = CAP_FRAME_HEIGHT - sum([y for _, y, _, _ in eyes]) / len(eyes)
            eye_heights.append(eye_average_height)

            if len(eye_heights) == SMA_N:
                sma_eye_heights = simple_moving_average(SMA_N, eye_heights)
            elif len(eye_heights) > SMA_N:
                add_simple_moving_average(sma_eye_heights, SMA_N, eye_heights)
            

        # Detect bad posture
        if sma_eye_heights and (sma_eye_heights[0] - sma_eye_heights[-1] > F * D / Z):
            res = messagebox.showinfo("BAD POSTURE!", "Sit up straight!\nCorrect your posture, then click ok.")
            if res == "ok":
                # Initialize state, and restart from begening
                eye_heights = []
                sma_eye_heights = []
                graph_x = np.arange(0, PLOT_NUM*PLOT_DELTA, PLOT_DELTA)
                continue

        # Plot eye heights
        graph_x += PLOT_DELTA
        ax.set_xlim((graph_x.min(), graph_x.max()))
        ax.set_ylim(0, CAP_FRAME_HEIGHT)

        if len(eye_heights) >= PLOT_NUM:
            eye_y = eye_heights[-PLOT_NUM:]
            eye_lines.set_data(graph_x, eye_y)
            plt.pause(.001)
        
        if len(sma_eye_heights) >= PLOT_NUM:
            sma_eye_y = sma_eye_heights[-PLOT_NUM:]
            sma_eye_lines.set_data(graph_x, sma_eye_y)
            plt.pause(.001)

        
        # Show result
        cv2.imshow(WINDOW_NAME, img_gray)

        # Quit with ESC Key
        if cv2.waitKey(1) & 0xFF == 27:
            break
    
    # End processing
    cv2.destroyAllWindows()
    cap.release()

référence

https://cvml-expertguide.net/2019/08/17/term-camera-model/

Recommended Posts

Détecter le retour du chat avec OpenCV
Je veux détecter des objets avec OpenCV
Binarisation avec OpenCV / Python
J'ai essayé de détecter rapidement un mouvement avec OpenCV
Faire pivoter les sprites avec OpenCV
Augmentation des données avec openCV
Essayez de détecter les poissons avec python + OpenCV2.4 (inachevé)
TopView facile avec OpenCV
Trébucher avec opencv3 de homebrew
Détecter l'état de port du masque avec OpenCV et Raspberry Pi
Reconnaissance faciale avec OpenCV de Python
"Traitement Apple" avec OpenCV3 + Python3
Édition d'image avec python OpenCV
Capture de caméra avec Python + OpenCV
[Python] Utilisation d'OpenCV avec Python (basique)
Binariser les données photo avec OpenCV
Chargement de la vidéo en boucle avec opencv
Détection des bords en temps réel avec OpenCV
Détection de visage avec Python + OpenCV
Obtenez des fonctionnalités d'image avec OpenCV
Reconnaissance faciale / coupe avec OpenCV
Essayez OpenCV avec Google Colaboratory
Création d'un classificateur en cascade avec opencv
Utiliser OpenCV avec Python @Mac
Reconnaissance d'image avec Keras + OpenCV
Détection de visage d'anime avec OpenCV
Briller la vie avec Python et OpenCV
Réseau neuronal avec OpenCV 3 et Python 3
[Python] Utilisation d'OpenCV avec Python (transformation d'image)
[Python] Utilisation d'OpenCV avec Python (détection des bords)
Effacez des couleurs spécifiques avec OpenCV + PySimpleGUI
Présentation d'OpenCV sur Mac avec homebrew
Programmation facile Python + OpenCV avec Canopy
Détectez les erreurs de grammaire anglaise avec link-grammar
Trouver les erreurs les plus simples avec OpenCV
[OpenCV] Identification personnelle avec photo du visage
Essayez la reconnaissance faciale avec python + OpenCV
Détecter les objets vidéo avec l'API Video Intelligence
Faire pivoter les sprites avec OpenCV # 2 ~ Maîtriser cv2.warpAffine () ~
Découpez le visage avec Python + OpenCV
Reconnaissance faciale avec caméra avec opencv3 + python2.7
Détection des fonctionnalités OpenCV avec Google Colaboratory
Recherche de mots composés à phase identique avec opencv
Charger une image gif avec Python + OpenCV
Détection de chat avec OpenCV (distribution de modèles)
Trouver la similitude d'image avec Python + OpenCV
Essayez de brouiller l'image avec opencv2
Utiliser OpenCV avec Python 3 dans Window
Dessinez une illustration avec Python + OpenCV
Suivre les balles de baseball avec Python + OpenCV
Segmentation basée sur un graphique avec Python + OpenCV
Reconnaissance d'objets avec openCV par traincascade
Dessinez des figures avec OpenCV et PIL
J'ai essayé la reconnaissance faciale avec OpenCV
Implémentation de la méthode de différence inter-trame avec OpenCV
Dessinez une flèche (vecteur) avec opencv / python
Etude de base d'OpenCV avec Python
Détection de visage avec Python + OpenCV (rotation invariante)
Cv2.imshow fonctionne désormais correctement avec OpenCV3 de Homebrew
Enregistrer la vidéo image par image avec Python OpenCV
Essayez d'utiliser l'appareil photo avec OpenCV de Python