Created
April 6, 2025 19:41
-
-
Save parkerlreed/bb65bf71b4868516236cc93c7f1161dd to your computer and use it in GitHub Desktop.
V380 camera viewer and PTZ control
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import time | |
import pygame | |
import cv2 | |
import numpy as np | |
from onvif import ONVIFCamera | |
from threading import Thread, Lock | |
# ---- Config ---- | |
IP = '192.168.86.21' | |
RTSP_URL = f'rtsp://{IP}/live/ch00_1' | |
PORT = 8899 | |
USER = 'admin' | |
PASSWORD = '123456' | |
SPEED = 0.5 | |
MOVE_DURATION = 0.4 # seconds | |
# ----------------- | |
def move(ptz, profile, x, y, zoom=0.0): | |
req = ptz.create_type('ContinuousMove') | |
req.ProfileToken = profile.token | |
req.Velocity = { | |
'PanTilt': {'x': x, 'y': y}, | |
'Zoom': {'x': zoom} | |
} | |
ptz.ContinuousMove(req) | |
time.sleep(MOVE_DURATION) | |
ptz.Stop({'ProfileToken': profile.token}) | |
def video_thread(rtsp_url, surface, lock): | |
cap = cv2.VideoCapture(rtsp_url) | |
if not cap.isOpened(): | |
print("⚠️ Failed to open RTSP stream") | |
return | |
while cap.isOpened(): | |
ret, frame = cap.read() | |
if not ret: | |
continue | |
# Resize and convert frame for Pygame | |
frame = cv2.resize(frame, (640, 360)) | |
frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) | |
frame = np.rot90(frame) # Optional: if orientation is off | |
frame = pygame.surfarray.make_surface(frame) | |
with lock: | |
surface.blit(frame, (0, 0)) | |
cap.release() | |
def main(): | |
pygame.init() | |
screen = pygame.display.set_mode((640, 400)) | |
pygame.display.set_caption("V380 PTZ + Video Feed") | |
font = pygame.font.SysFont(None, 24) | |
clock = pygame.time.Clock() | |
cam = ONVIFCamera(IP, PORT, USER, PASSWORD) | |
media = cam.create_media_service() | |
ptz = cam.create_ptz_service() | |
profile = media.GetProfiles()[0] | |
rtsp_surface = pygame.Surface((640, 360)) | |
lock = Lock() | |
# Start RTSP feed in background | |
Thread(target=video_thread, args=(RTSP_URL, rtsp_surface, lock), daemon=True).start() | |
running = True | |
while running: | |
screen.fill((0, 0, 0)) | |
with lock: | |
screen.blit(rtsp_surface, (0, 0)) | |
help_text = font.render("Arrow keys to move PTZ, ESC to quit", True, (255, 255, 255)) | |
screen.blit(help_text, (10, 365)) | |
pygame.display.flip() | |
for event in pygame.event.get(): | |
if event.type == pygame.QUIT: | |
running = False | |
elif event.type == pygame.KEYDOWN: | |
if event.key == pygame.K_ESCAPE: | |
running = False | |
elif event.key == pygame.K_UP: | |
move(ptz, profile, 0.0, SPEED) | |
elif event.key == pygame.K_DOWN: | |
move(ptz, profile, 0.0, -SPEED) | |
elif event.key == pygame.K_LEFT: # SWAPPED | |
move(ptz, profile, -SPEED, 0.0) | |
elif event.key == pygame.K_RIGHT: # SWAPPED | |
move(ptz, profile, SPEED, 0.0) | |
clock.tick(30) | |
pygame.quit() | |
if __name__ == '__main__': | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment