Wenn Sie sich auf Ihre Schreibtischarbeit konzentrieren, erhalten Sie möglicherweise eine Katze zurück, ohne es zu merken. Dies gilt umso mehr für die Fernarbeit, da keine Augen einer anderen Person vorhanden sind.
Also habe ich einen Mechanismus entwickelt, der Sie benachrichtigt, wenn sich Ihre Haltung verschlechtert!
Python 3.7.4 Webkamera (Logitech HD Webcam C615)
OpenCV
Installation
$ pip install opencv-python
Zuerst werden wir das Kamerabild aufnehmen und die Augen erkennen.
Das Folgende ist eine Referenz für die Objekterkennungsmethode in OpenCV.
"Gesichtserkennung mit 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
Da wir diesmal die Augen erkennen möchten, verwenden wir "haarcascade_eye.xml" als Klassifikator. Sie können es über den unten stehenden Link herunterladen.
https://github.com/opencv/opencv/tree/master/data/haarcascades
capture.py
import numpy as np
import cv2
#Kameragerätnummer
#Verfügbare Kameras sind ab 0 bestellt
DEVICE_ID = 0
#Klassifikator auswählen
cascade = cv2.CascadeClassifier('haarcascade_eye.xml')
#Nehmen Sie Kameramaterial auf
cap = cv2.VideoCapture(DEVICE_ID, cv2.CAP_DSHOW)
while cap.isOpened():
#Holen Sie sich Rahmen
ret, frame = cap.read()
#In Graustufen konvertieren
img_gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
#Auge erkennen
eyes = cascade.detectMultiScale(img_gray, minSize=(30, 30))
#Schließen Sie das erkannte Auge in ein Quadrat ein
for (x, y, w, h) in eyes:
color = (255, 0, 0)
cv2.rectangle(img_gray, (x, y), (x+w, y+h), color, thickness=3)
#Anzeige
cv2.imshow('capture', img_gray)
#Verlassen Sie die Schleife mit der ESC-Taste
if cv2.waitKey(1) & 0xFF == 27:
break
#Verarbeitung beenden
cv2.destroyAllWindows()
cap.release()
Zeile 25, "y", ist die erfasste Augenhöheninformation. Daher ist es in Ordnung, wenn Sie diesen Wert aufzeichnen und sehen, wie er fällt!
Nun, ich habe die Augenhöhe, aber es ist nicht so gut, diesen Wert zu verwenden, wie er ist. Es ist nicht so, dass es als Rücken einer Katze beurteilt wird, wenn man nur einen Moment nach unten schaut, also werde ich zu diesem Zweck den Durchschnittswert für einen bestimmten Zeitraum behandeln.
Hier kommt der "gleitende Durchschnitt" ins Spiel. Diejenigen, die Aktien und Devisen machen, sind vielleicht vertraut. Dieses Mal habe ich den einfachsten "__ einfachen gleitenden Durchschnitt __" unter den gleitenden Durchschnitten verwendet.
Simple Moving Average (SMA) ist ein ungewichteter einfacher Durchschnitt der letzten n Daten. Beispielsweise ist der einfache gleitende Durchschnitt der Schlusskurse für 10 Tage der Durchschnitt der Schlusskurse der letzten 10 Tage. Setzen Sie ihre Schlusskurse auf $ {\ displaystyle p_ {M}} p_ {{M}}, {\ displaystyle p_ {M-1}} p_ {{M-1}}, ..., {\ displaystyle p_ {M-9 }} p_ {{M-9}} $, dann lautet die Formel für den einfachen gleitenden Durchschnitt SMA (p, 10):
{\text{SMA}}{M}={p{M}+p_{M-1}+\cdots +p_{M-9} \over 10}
> Um den einfachen gleitenden Durchschnitt des nächsten Tages zu ermitteln, fügen Sie einen neuen Schlusskurs hinzu und entfernen Sie den ältesten Schlusskurs. Mit anderen Worten, bei dieser Berechnung ist es nicht erforderlich, die Summe neu zu berechnen.
>```math
{\text{SMA}}_{{\mathrm {today}}}={\text{SMA}}_{{\mathrm {yesterday}}}-{p_{{M-n+1}} \over n}+{p_{{M+1}} \over n}
Der Punkt ist, dass Sie die letzten n Teile jedes Elements der Daten mitteln können.
Wenn die Differenz zwischen dem Anfang und dem Ende der Zeitreihendaten dieses einfachen gleitenden Durchschnitts größer als ein bestimmter Schwellenwert ist, kann dies als Katzenrückstand beurteilt werden.
Die Augenhöheninformation "y", über die ich bisher gesprochen habe, ist nur die Positionsinformation der Pixel des Kamerabildes. Daher ist es notwendig, separat zu berechnen, wie viel cm die Änderung des Wertes von "y" in der realen Welt entspricht.
Daher werde ich überlegen, was als Projektionskonvertierung bezeichnet wird. Da das Bild von der Kamera zweidimensional ist, muss die dreidimensionale Welt bei der Visualisierung auf eine zweidimensionale Ebene projiziert werden. Betrachten Sie es diesmal als perspektivische Projektionskonvertierung.
Perspektivische Projektionstransformationen sind $ \ left (\ frac {x} {z}, \ frac {y} {z}, 0 \ right) $ für dreidimensionale Koordinaten $ (x, y, z) $. Konvertieren. Ich denke, es ist leicht zu verstehen, weil es der Intuition entspricht, dass "entfernte Dinge klein aussehen".
Daher ist die Beziehung zwischen dem Höhenunterschied des Kamerabildes $ \ Delta y_d $ (px) und dem Höhenunterschied des realen $ \ Delta y_v $ (cm) wie folgt.
\begin{equation}
\frac{\Delta y_d}{f} = \frac{\Delta y_v}{z_v} \tag{1}
\end{equation}
$ z_v $ ist die Entfernung zwischen der Kamera und dem Objekt und $ f $ ist die Brennweite der Kamera. Angesichts der Ähnlichkeit von Dreiecken ist es einfach.
Da die Brennweite der Kamera je nach Modell der Kamera variiert, muss sie durch Kalibrierung ermittelt werden. Die Kamerakalibrierung wird auch von OpenCV bereitgestellt.
"Kamerakalibrierung" http://whitewell.sakura.ne.jp/OpenCV/py_tutorials/py_calib3d/py_calibration/py_calibration.html
Der Inhalt der durch Kalibrierung erhaltenen internen Parameter der Kamera ist wie folgt, und die Brennweite kann von hier aus ermittelt werden.
K = \left[
\begin{array}{ccc}
f & 0 & x_c \\
0 & f & y_c \\
0 & 0 & 1
\end{array}
\right]
$ x_c, y_c $ sind die Mittelpunkte der Projektionsebene.
Diese Kalibrierung ist jedoch etwas mühsam, da ich ein Bild des Schachbretts erstellen muss, das tatsächlich mit der Kamera aufgenommen wurde. .. ..
Die von mir verwendete Webkamera (Logitech HD Webcam C615) kostet ungefähr 500 $ f $, also als Referenz.
Sobald Sie $ f $ erhalten, sind Sie fertig.
In den Zeitreihendaten des einfachen gleitenden Durchschnitts auf Augenhöhe beträgt der Unterschied zwischen Anfang und Ende $ \ Delta y_d $, und es ist nur erforderlich, nach Gleichung (1) zu urteilen. Der Abstand $ z_v $ von der Kamera zum Computer beträgt ca. 45 cm. Außerdem ist der Schwellenwert $ \ Delta y_v $ für den Unterschied in der Augenhöhe in der realen Welt auf 3 cm festgelegt.
Gezeichnet mit Pyplot zur Datenvisualisierung.
Außerdem verwenden Warnungen die Nachrichtenbox von 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