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!
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!
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.
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.
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.
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.
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()
https://cvml-expertguide.net/2019/08/17/term-camera-model/
Recommended Posts