Revert "Changed color camera back to color, set all cameras to 20ms shutter speed"
This reverts commit 413590d1a2.
This commit is contained in:
parent
413590d1a2
commit
97c7772a4c
@ -11,7 +11,7 @@ TARGET_NUM_CAMS = 3
|
||||
DEFAULT_W = 1280
|
||||
DEFAULT_H = 720
|
||||
|
||||
# --- PART 1: DETECTION (Unchanged) ---
|
||||
# --- PART 1: DETECTION ---
|
||||
def scan_connected_cameras():
|
||||
print("--- Scanning for Basler Cameras ---")
|
||||
detection_script = """
|
||||
@ -23,58 +23,45 @@ try:
|
||||
if not devices:
|
||||
print("NONE")
|
||||
else:
|
||||
results = []
|
||||
for i in range(len(devices)):
|
||||
cam = pylon.InstantCamera(tl_factory.CreateDevice(devices[i]))
|
||||
serials = [d.GetSerialNumber() for d in devices]
|
||||
cam = pylon.InstantCamera(tl_factory.CreateDevice(devices[0]))
|
||||
cam.Open()
|
||||
serial = cam.GetDeviceInfo().GetSerialNumber()
|
||||
model = cam.GetDeviceInfo().GetModelName()
|
||||
is_color = model.endswith("c") or "Color" in model
|
||||
w = cam.Width.GetValue()
|
||||
h = cam.Height.GetValue()
|
||||
binning = 0
|
||||
try:
|
||||
cam.BinningHorizontal.Value = 2
|
||||
cam.BinningVertical.Value = 2
|
||||
w = cam.Width.GetValue()
|
||||
h = cam.Height.GetValue()
|
||||
cam.BinningHorizontal.Value = 1
|
||||
cam.BinningVertical.Value = 1
|
||||
binning = 1
|
||||
except: pass
|
||||
current_fmt = cam.PixelFormat.GetValue()
|
||||
supported = 1
|
||||
except:
|
||||
w = cam.Width.GetValue()
|
||||
h = cam.Height.GetValue()
|
||||
supported = 0
|
||||
cam.Close()
|
||||
results.append(f"{serial}:{w}:{h}:{binning}:{1 if is_color else 0}:{model}:{current_fmt}")
|
||||
print("|".join(results))
|
||||
except Exception: print("NONE")
|
||||
print(f"{','.join(serials)}|{w}|{h}|{supported}")
|
||||
except Exception:
|
||||
print("NONE")
|
||||
"""
|
||||
try:
|
||||
result = subprocess.run([sys.executable, "-c", detection_script], capture_output=True, text=True)
|
||||
output = result.stdout.strip()
|
||||
if "NONE" in output or not output: return []
|
||||
camera_list = []
|
||||
entries = output.split('|')
|
||||
for entry in entries:
|
||||
parts = entry.split(':')
|
||||
camera_list.append({
|
||||
"serial": parts[0], "width": int(parts[1]), "height": int(parts[2]),
|
||||
"binning": (parts[3] == '1'), "is_color": (parts[4] == '1'), "model": parts[5]
|
||||
})
|
||||
return camera_list
|
||||
except: return []
|
||||
if "NONE" in output or not output:
|
||||
return [], DEFAULT_W, DEFAULT_H, False
|
||||
parts = output.split('|')
|
||||
return parts[0].split(','), int(parts[1]), int(parts[2]), (parts[3] == '1')
|
||||
except: return [], DEFAULT_W, DEFAULT_H, False
|
||||
|
||||
DETECTED_CAMS = scan_connected_cameras()
|
||||
ACTUAL_CAMS_COUNT = len(DETECTED_CAMS)
|
||||
|
||||
# --- RESOLUTION LOGIC ---
|
||||
if ACTUAL_CAMS_COUNT > 0:
|
||||
MASTER_W = DETECTED_CAMS[0]['width']
|
||||
MASTER_H = DETECTED_CAMS[0]['height']
|
||||
else:
|
||||
MASTER_W = DEFAULT_W
|
||||
MASTER_H = DEFAULT_H
|
||||
DETECTED_SERIALS, CAM_W, CAM_H, BINNING_SUPPORTED = scan_connected_cameras()
|
||||
ACTUAL_CAMS_COUNT = len(DETECTED_SERIALS)
|
||||
|
||||
# --- RESOLUTION & LAYOUT ---
|
||||
INTERNAL_WIDTH = 1280
|
||||
scale = INTERNAL_WIDTH / MASTER_W
|
||||
INTERNAL_HEIGHT = int(MASTER_H * scale)
|
||||
if ACTUAL_CAMS_COUNT > 0:
|
||||
scale = INTERNAL_WIDTH / CAM_W
|
||||
INTERNAL_HEIGHT = int(CAM_H * scale)
|
||||
else:
|
||||
INTERNAL_HEIGHT = 720
|
||||
if INTERNAL_HEIGHT % 2 != 0: INTERNAL_HEIGHT += 1
|
||||
|
||||
WEB_WIDTH = 1280
|
||||
@ -83,9 +70,7 @@ scale_tiled = WEB_WIDTH / total_source_width
|
||||
WEB_HEIGHT = int(INTERNAL_HEIGHT * scale_tiled)
|
||||
if WEB_HEIGHT % 2 != 0: WEB_HEIGHT += 1
|
||||
|
||||
print(f"LAYOUT: {TARGET_NUM_CAMS} Slots | Detected: {ACTUAL_CAMS_COUNT}")
|
||||
for c in DETECTED_CAMS:
|
||||
print(f" - Cam {c['serial']} ({c['model']}): {'COLOR' if c['is_color'] else 'MONO'}")
|
||||
print(f"LAYOUT: {TARGET_NUM_CAMS} Slots | Detected: {ACTUAL_CAMS_COUNT} Cams")
|
||||
|
||||
# --- FLASK & GSTREAMER ---
|
||||
import gi
|
||||
@ -122,6 +107,7 @@ class GStreamerPipeline(threading.Thread):
|
||||
if not sample: return Gst.FlowReturn.ERROR
|
||||
|
||||
frame_count += 1
|
||||
# Calculate FPS every 30 frames
|
||||
if frame_count % 30 == 0:
|
||||
elapsed = time.time() - start_time
|
||||
current_fps = 30 / elapsed if elapsed > 0 else 0
|
||||
@ -139,80 +125,52 @@ class GStreamerPipeline(threading.Thread):
|
||||
return Gst.FlowReturn.OK
|
||||
|
||||
def build_pipeline(self):
|
||||
# 1. CAMERA SETTINGS
|
||||
# Note: We run cameras at 60 FPS for internal stability
|
||||
cam_settings = (
|
||||
"cam::TriggerMode=Off "
|
||||
"cam::AcquisitionFrameRateEnable=true cam::AcquisitionFrameRate=60.0 "
|
||||
"cam::ExposureAuto=Off "
|
||||
"cam::ExposureTime=20000.0 "
|
||||
"cam::GainAuto=Continuous "
|
||||
"cam::DeviceLinkThroughputLimitMode=Off "
|
||||
)
|
||||
if BINNING_SUPPORTED:
|
||||
cam_settings += "cam::BinningHorizontal=2 cam::BinningVertical=2 "
|
||||
|
||||
sources_str = ""
|
||||
|
||||
for i in range(TARGET_NUM_CAMS):
|
||||
if i < len(DETECTED_CAMS):
|
||||
cam_info = DETECTED_CAMS[i]
|
||||
serial = cam_info['serial']
|
||||
is_color = cam_info['is_color']
|
||||
if i < len(DETECTED_SERIALS):
|
||||
# --- REAL CAMERA SOURCE ---
|
||||
serial = DETECTED_SERIALS[i]
|
||||
print(f"Slot {i}: Linking Camera {serial}")
|
||||
|
||||
print(f"Slot {i}: Linking {serial} [{'Color' if is_color else 'Mono'}]")
|
||||
|
||||
# --- 1. BASE SETTINGS (Common) ---
|
||||
# We DISABLE Throughput Limit to allow high bandwidth
|
||||
base_settings = (
|
||||
f"pylonsrc device-serial-number={serial} "
|
||||
"cam::TriggerMode=Off "
|
||||
"cam::AcquisitionFrameRateEnable=true cam::AcquisitionFrameRate=60.0 "
|
||||
"cam::DeviceLinkThroughputLimitMode=Off "
|
||||
)
|
||||
|
||||
# Pre-scaler
|
||||
pre_scale = (
|
||||
"nvvideoconvert compute-hw=1 ! "
|
||||
f"video/x-raw(memory:NVMM), format=NV12, width={INTERNAL_WIDTH}, height={INTERNAL_HEIGHT}, framerate=60/1 ! "
|
||||
)
|
||||
|
||||
if is_color:
|
||||
# --- 2A. COLOR SETTINGS (High Speed) ---
|
||||
# FIX: Force ExposureTime=20000.0 (20ms) even for Color.
|
||||
# If we leave it on Auto, it will slow down the Mono cameras.
|
||||
# We rely on 'GainAuto' to make the image bright enough.
|
||||
color_settings = (
|
||||
f"{base_settings} "
|
||||
"cam::ExposureAuto=Off cam::ExposureTime=20000.0 "
|
||||
"cam::GainAuto=Continuous "
|
||||
"cam::Width=1920 cam::Height=1080 cam::OffsetX=336 cam::OffsetY=484 "
|
||||
"cam::PixelFormat=BayerBG8 " # Force Format
|
||||
)
|
||||
|
||||
source = (
|
||||
f"{color_settings} ! "
|
||||
"bayer2rgb ! " # Debayer
|
||||
"videoconvert ! "
|
||||
"video/x-raw,format=RGBA ! "
|
||||
"nvvideoconvert compute-hw=1 ! "
|
||||
f"video/x-raw(memory:NVMM), format=NV12 ! "
|
||||
f"{pre_scale}"
|
||||
f"m.sink_{i} "
|
||||
)
|
||||
else:
|
||||
# --- 2B. MONO SETTINGS (High Speed) ---
|
||||
# Force ExposureTime=20000.0
|
||||
mono_settings = (
|
||||
f"{base_settings} "
|
||||
"cam::ExposureAuto=Off cam::ExposureTime=20000.0 "
|
||||
"cam::GainAuto=Continuous "
|
||||
)
|
||||
if cam_info['binning']:
|
||||
mono_settings += "cam::BinningHorizontal=2 cam::BinningVertical=2 "
|
||||
|
||||
source = (
|
||||
f"{mono_settings} ! "
|
||||
f"pylonsrc device-serial-number={serial} {cam_settings} ! "
|
||||
"video/x-raw,format=GRAY8 ! "
|
||||
"videoconvert ! "
|
||||
"video/x-raw,format=I420 ! "
|
||||
"nvvideoconvert compute-hw=1 ! "
|
||||
f"video/x-raw(memory:NVMM), format=NV12 ! "
|
||||
"video/x-raw(memory:NVMM) ! "
|
||||
f"{pre_scale}"
|
||||
f"m.sink_{i} "
|
||||
)
|
||||
else:
|
||||
# --- DISCONNECTED PLACEHOLDER ---
|
||||
print(f"Slot {i}: Creating Placeholder (Synchronized)")
|
||||
|
||||
# FIX 1: Add 'videorate' to enforce strict timing on the fake source
|
||||
# This prevents the placeholder from running too fast/slow and jittering the muxer
|
||||
|
||||
source = (
|
||||
f"videotestsrc pattern=black is-live=true ! "
|
||||
f"videorate ! "
|
||||
f"videorate ! " # <--- TIMING ENFORCER
|
||||
f"video/x-raw,width={INTERNAL_WIDTH},height={INTERNAL_HEIGHT},format=I420,framerate=60/1 ! "
|
||||
f"textoverlay text=\"DISCONNECTED\" valignment=center halignment=center font-desc=\"Sans, 48\" ! "
|
||||
"nvvideoconvert compute-hw=1 ! "
|
||||
@ -223,21 +181,28 @@ class GStreamerPipeline(threading.Thread):
|
||||
sources_str += source
|
||||
|
||||
# 3. MUXER & PROCESSING
|
||||
# FIX 2: batched-push-timeout=33000
|
||||
# This tells the muxer: "If you have data, send it every 33ms (30fps). Don't wait forever."
|
||||
|
||||
# FIX 3: Output Videorate
|
||||
# We process internally at 60fps (best for camera driver), but we DROP to 30fps
|
||||
# for the web stream. This makes the network stream buttery smooth and consistent.
|
||||
|
||||
processing = (
|
||||
f"nvstreammux name=m batch-size={TARGET_NUM_CAMS} width={INTERNAL_WIDTH} height={INTERNAL_HEIGHT} "
|
||||
f"live-source=1 batched-push-timeout=33000 ! "
|
||||
f"live-source=1 batched-push-timeout=33000 ! " # <--- TIMEOUT FIX
|
||||
f"nvmultistreamtiler width={WEB_WIDTH} height={WEB_HEIGHT} rows=1 columns={TARGET_NUM_CAMS} ! "
|
||||
"nvvideoconvert compute-hw=1 ! "
|
||||
"video/x-raw(memory:NVMM) ! "
|
||||
"videorate drop-only=true ! "
|
||||
"video/x-raw(memory:NVMM), framerate=30/1 ! "
|
||||
"videorate drop-only=true ! " # <--- DROPPING FRAMES CLEANLY
|
||||
"video/x-raw(memory:NVMM), framerate=30/1 ! " # <--- Force 30 FPS Output
|
||||
f"nvjpegenc quality=60 ! "
|
||||
"appsink name=sink emit-signals=True sync=False max-buffers=1 drop=True"
|
||||
)
|
||||
|
||||
pipeline_str = f"{sources_str} {processing}"
|
||||
|
||||
print(f"Launching Optimized Pipeline (All Cams Forced to 20ms Shutter)...")
|
||||
print(f"Launching SMOOTH Pipeline...")
|
||||
self.pipeline = Gst.parse_launch(pipeline_str)
|
||||
appsink = self.pipeline.get_by_name("sink")
|
||||
appsink.connect("new-sample", self.on_new_sample)
|
||||
@ -260,7 +225,7 @@ def index():
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Basler Final Feed</h1>
|
||||
<h1>Basler 3-Cam (Smooth)</h1>
|
||||
<div class="container">
|
||||
<div class="hud" id="fps-counter">FPS: --</div>
|
||||
<img src="{{ url_for('video_feed') }}">
|
||||
@ -284,7 +249,8 @@ def video_feed():
|
||||
with buffer_lock:
|
||||
if frame_buffer:
|
||||
yield (b'--frame\r\n' b'Content-Type: image/jpeg\r\n\r\n' + frame_buffer + b'\r\n')
|
||||
time.sleep(0.016)
|
||||
# Sleep 33ms (30 FPS)
|
||||
time.sleep(0.033)
|
||||
count += 1
|
||||
if count % 200 == 0: gc.collect()
|
||||
return Response(generate(), mimetype='multipart/x-mixed-replace; boundary=frame')
|
||||
|
||||
Loading…
Reference in New Issue
Block a user