2024-02-05 16:54:50 -05:00
#import copy
2020-01-01 13:55:01 +01:00
import os
2024-02-05 16:54:50 -05:00
#limits the threads used by numpy to 1
#setting this and the landmarks model to use 1 means the cpu usage is pretty low
os . environ [ " OMP_NUM_THREADS " ] = str ( 1 )
2020-01-01 13:55:01 +01:00
import argparse
2020-08-11 13:09:49 +02:00
import traceback
2024-02-05 16:54:50 -05:00
import threading
import queue
import time
from tracker import Tracker
import multiprocessing
import webcam
import vts
import preview
#overall, my changes seem to have memory usage up from about 140mb to 180mb
#but cpu usage seems to be down
#and it's more resilent to low performance scenarios
2020-01-01 13:55:01 +01:00
parser = argparse . ArgumentParser ( formatter_class = argparse . ArgumentDefaultsHelpFormatter )
parser . add_argument ( " -i " , " --ip " , help = " Set IP address for sending tracking data " , default = " 127.0.0.1 " )
parser . add_argument ( " -p " , " --port " , type = int , help = " Set port for sending tracking data " , default = 11573 )
2024-02-05 16:54:50 -05:00
parser . add_argument ( " -W " , " --width " , type = int , help = " Set raw RGB width " , default = 640 )
parser . add_argument ( " -H " , " --height " , type = int , help = " Set raw RGB height " , default = 360 )
2021-10-01 02:35:00 +10:00
parser . add_argument ( " -F " , " --fps " , type = int , help = " Set camera frames per second " , default = 24 )
2024-02-05 16:54:50 -05:00
parser . add_argument ( " -c " , " --capture " , help = " Set camera ID (0, 1...) or video file " , default = 0 )
2021-05-29 00:33:06 +01:00
parser . add_argument ( " -M " , " --mirror-input " , action = " store_true " , help = " Process a mirror image of the input video " )
2024-02-05 16:54:50 -05:00
parser . add_argument ( " -t " , " --threshold " , type = float , help = " Set minimum confidence threshold for face tracking " , default = 0.6 )
2020-09-02 18:42:25 +02:00
parser . add_argument ( " -d " , " --detection-threshold " , type = float , help = " Set minimum confidence threshold for face detection " , default = 0.6 )
2020-01-01 13:55:01 +01:00
parser . add_argument ( " -s " , " --silent " , type = int , help = " Set this to 1 to prevent text output on the console " , default = 0 )
2024-02-05 16:54:50 -05:00
parser . add_argument ( " --model " , type = int , help = " This can be used to select the tracking model. Higher numbers are models with better tracking quality, but slower speed, except for model 4, which is wink optimized. " , default = 3 , choices = [ 0 , 1 , 2 , 3 , 4 ] )
parser . add_argument ( " --preview " , type = int , help = " Preview the frames sent to the tracker " , default = 0 )
2020-01-01 13:55:01 +01:00
args = parser . parse_args ( )
2020-09-26 23:49:54 +02:00
2024-02-05 16:54:50 -05:00
def messaging ( messageQueue ) :
while True :
message = messageQueue . get ( )
print ( message )
2020-01-01 13:55:01 +01:00
2024-02-05 16:54:50 -05:00
#processing args
#For Vtube Studio: ip 127.0.0.1 port 11573
2020-01-01 13:55:01 +01:00
target_ip = args . ip
target_port = args . port
2021-10-01 02:35:00 +10:00
fps = args . fps
2024-02-05 16:54:50 -05:00
height = args . height
width = args . width
previewFlag = ( args . preview == 1 )
mirror_input = args . mirror_input
#I make a minor tweak to the framerate to help the camera out
#it's an inpercetible amount of time per frame
#but my hope is it keeps things from riding up against what the webcam can put out
target_duration = 1. / ( fps - 0.01 )
targetBrightness = 0.55
silent = ( args . silent == 1 )
#increating the webcam queue to compensate for longer frame times at different settings
frameQueueSize = 1
if height > 480 :
frameQueueSize = 3
#My testing has shown that it can take upwards of 80ms when capturing images above 480p
#each frame is 33ms, so I'd need 3 frames, or 99ms to cover that without skipping a frame
if fps > 30 :
frameQueueSize = frameQueueSize * 2
#I'm just going to assume the webcam does not work well above 30fps
#creating queues
frameQueue = multiprocessing . Queue ( maxsize = frameQueueSize )
faceQueue = multiprocessing . Queue ( maxsize = 1 )
messageQueue = queue . Queue ( )
packetQueue = queue . Queue ( )
#creating threads
#probably doesn't need it's own thread, but it can't hurt
messageThread = threading . Thread ( target = messaging , args = [ messageQueue ] , )
messageThread . daemon = True
messageThread . start ( )
#I want to decouple the requests so they don't block anything else if they're slow
VTS = vts . VTS ( target_ip , target_port , silent , height , width , packetQueue , messageQueue )
packetSenderThread = threading . Thread ( target = VTS . packetSender , args = [ ] , )
packetSenderThread . daemon = True
packetSenderThread . start ( )
#creating processes
#not a very active process, it's blocked by the main thread 90% of the time
#but putting it in it's own process means there's a little bit less occupying the same core as the main thread
#which helps when the system is under load
webcamProcess = multiprocessing . Process ( target = webcam . startProcess , args = ( frameQueue , faceQueue , fps , targetBrightness , width , height , mirror_input , messageQueue , ) )
webcamProcess . daemon = True
webcamProcess . start ( )
#This is it's own process because it's super weird performance wise
if previewFlag :
previewFrameQueue = multiprocessing . Queue ( maxsize = 1 )
previewProcess = multiprocessing . Process ( target = preview . startProcess , args = ( previewFrameQueue , target_duration , ) )
previewProcess . daemon = True
previewProcess . start ( )
2020-01-01 13:55:01 +01:00
frame_count = 0
2024-02-05 16:54:50 -05:00
peak_frame_time = 0.0
total_active_time = 0.0
total_run_time = 0.0
frame_start = 0.0
peak_time_between = 0.0
peak_camera_latency = 0.0
total_camera_latency = 0.0
failures = 0
tracker = Tracker ( width , height , messageQueue , threshold = args . threshold , silent = silent , model_type = args . model , detection_threshold = args . detection_threshold )
#don't start until the webcam is ready
while frameQueue . qsize ( ) < 1 :
time . sleep ( 0.33 )
2020-01-01 13:55:01 +01:00
try :
2024-02-05 16:54:50 -05:00
while True :
#clearing these so there's no stale data to confuse the checks
packet = None
face = None
frame_start = time . perf_counter ( )
2020-01-01 13:55:01 +01:00
frame_count + = 1
2024-02-05 16:54:50 -05:00
#if the camera process doesn't have a frame for us we just skip everything
#in my testing the camera was consistently waiting on the main process
#so if it's *that far behind* it probably needs a frame or two to catch up
if frameQueue . qsize ( ) > 0 :
frame , camera_latency = frameQueue . get ( )
#If I don't wait a few frames to start tracking I get wild peak frame times, like 500ms
if frame_count > 5 :
peak_camera_latency = max ( camera_latency , peak_camera_latency )
total_camera_latency + = camera_latency
if previewFlag :
#there's no reason to ever wait on this process or give it much of a queue
if previewFrameQueue . qsize ( ) < 1 :
previewFrameQueue . put ( frame )
try :
face , face_center , face_radius = tracker . predict ( frame )
failures = 0
except Exception as e :
if e . __class__ == KeyboardInterrupt :
if not silent :
messageQueue . put ( " Quitting " )
break
traceback . print_exc ( )
failures + = 1
if failures > 30 :
2020-08-11 12:29:31 +02:00
break
2020-03-03 18:48:52 +01:00
2024-02-05 16:54:50 -05:00
if face is not None :
packet = VTS . preparePacket ( face )
#this can maybe cause weirdness in the gamma calculations
#but I don't want to let that build a queue
if faceQueue . qsize ( ) < 1 :
faceQueue . put ( [ face_center , face_radius ] )
else :
messageQueue . put ( " Camera behind, skipping frame " )
duration = time . perf_counter ( ) - frame_start
total_active_time + = duration
peak_frame_time = max ( peak_frame_time , duration )
if duration < target_duration :
time . sleep ( target_duration - duration )
else :
#all caps because if this is consistently happening it's an issue
messageQueue . put ( " CANNOT MAINTAIN FRAMERATE! " )
peak_time_between = max ( peak_time_between , time . perf_counter ( ) - frame_start )
total_run_time + = time . perf_counter ( ) - frame_start
#If we don't have something to send to Vtube Studio we don't
if ( packet is not None ) :
packetQueue . put ( packet )
else :
messageQueue . put ( " No data sent to VTS " )
2020-08-21 22:47:02 +02:00
2020-01-01 13:55:01 +01:00
except KeyboardInterrupt :
2024-02-05 16:54:50 -05:00
if not silent :
2020-01-01 13:55:01 +01:00
print ( " Quitting " )
2024-02-05 16:54:50 -05:00
#printing statistics on close
#it makes identifying problems easier
#480p generally results in camera latencies between 0.5ms and 2.0ms
print ( f " Peak camera latency: { ( peak_camera_latency * 1000 ) : .3f } ms " )
print ( f " Average camera latency: { ( total_camera_latency * 1000 / ( frame_count - 5 ) ) : .3f } ms " )
#The longest time a full cycle of the main loop takes, including sleep
#indicates issues when higher than average time between frames by a significant amount
print ( f " Peak time between frames: { ( peak_time_between * 1000 ) : .3f } ms " )
#at 30fps this should be a hair above 33.333ms
print ( f " Average time between frames: { ( total_run_time * 1000 / ( frame_count ) ) : .3f } ms " )
#These measure how long the main loop takes *not including sleep*
#tends to be slightly longer than the average time given by the tracking status by 1ms to 2ms
#because it includes detection frames and all the other minor operations that need to happen every frame
print ( f " Peak frame time: { ( peak_frame_time * 1000 ) : .3f } ms " )
print ( f " Average frame time: { ( ( total_active_time ) * 1000 / ( frame_count ) ) : .3f } ms " )
print ( f " Run time (seconds): { total_run_time : .2f } s \n Frames: { frame_count } " )
webcamProcess . terminate ( )
if previewFlag :
previewProcess . terminate ( )
os . _exit ( 0 )