251 lines
7.6 KiB
Python
251 lines
7.6 KiB
Python
import math
|
|
import os
|
|
import time
|
|
import ctypes as C
|
|
from ctypes import wintypes as W
|
|
|
|
import numpy as np
|
|
import cv2
|
|
|
|
# =========================
|
|
# CONFIG
|
|
# =========================
|
|
SDK_DIR = os.path.join(os.path.dirname(__file__), "dlls")
|
|
DLL_NAME = "VT_SDK64.dll"
|
|
|
|
TIMEOUT_MS = 2000
|
|
WINDOW_NAME = "GAL5000 4CH Preview (E=AEC toggle, G=AGC toggle, Q=quit)"
|
|
|
|
# Camera scan/open
|
|
DEVICE_UDEF = 0
|
|
DEVICE_INDEX = 0
|
|
DATA_RAW = 0
|
|
|
|
# RAW geometry (como você já capturou)
|
|
RAW_W = 2592
|
|
RAW_H = 2056
|
|
|
|
# Bayer+NIR pattern (2x2):
|
|
# R G
|
|
# IR B
|
|
# => R = [0::2,0::2], G=[0::2,1::2], IR=[1::2,0::2], B=[1::2,1::2]
|
|
|
|
# =========================
|
|
# PARAM IDs (VT_Param.h)
|
|
# =========================
|
|
BUF_SIZE = 256
|
|
|
|
PARAM_ID_SENSOR_EXPOSURETIMERAW = 0x00003010
|
|
PARAM_ID_SENSOR_GAINANALOGRAW = 0x00003020
|
|
PARAM_ID_SENSOR_EXPOSUREAUTOENABLE = 0x00003011
|
|
PARAM_ID_SENSOR_GAINANALOGAUTOENABLE = 0x00003021
|
|
PARAM_ID_SENSOR_GAINANALOGAGCMAX = 0x00003022
|
|
PARAM_ID_COMMON_DISPLAYFPS = 0x00000203 # float R (média efetiva) :contentReference[oaicite:9]{index=9}
|
|
|
|
|
|
# PARAM_VALUETYPE
|
|
VALUE_INT = 0
|
|
VALUE_FLOAT = 1
|
|
VALUE_STR = 2
|
|
|
|
|
|
# =========================
|
|
# STRUCTS (mínimo necessário)
|
|
# =========================
|
|
class VT_FRAMEINFO(C.Structure):
|
|
_fields_ = [
|
|
("lFrameID", W.DWORD),
|
|
("lBufSize", W.DWORD),
|
|
("lWidth", W.DWORD),
|
|
("lHeight", W.DWORD),
|
|
("lPixBits", C.c_ubyte),
|
|
("_pad0", C.c_ubyte * 3),
|
|
("pBufPtr", C.POINTER(C.c_ubyte)),
|
|
("lFrameStatus", W.DWORD),
|
|
("lPixType", W.DWORD),
|
|
("lTimeStamp", W.DWORD),
|
|
("_reserve", W.DWORD * 8),
|
|
]
|
|
|
|
class VT_DEVPARAM(C.Structure):
|
|
_fields_ = [
|
|
("bUseName", W.BOOL),
|
|
("lParamByID", W.DWORD),
|
|
("lParamByName", C.c_char * BUF_SIZE),
|
|
]
|
|
|
|
def devparam_by_id(pid: int) -> VT_DEVPARAM:
|
|
p = VT_DEVPARAM()
|
|
p.bUseName = False
|
|
p.lParamByID = pid
|
|
p.lParamByName = b"" # <- CORRETO: bytes (fica zerado / string vazia)
|
|
return p
|
|
|
|
# =========================
|
|
# DLL LOAD + prototypes
|
|
# =========================
|
|
os.add_dll_directory(SDK_DIR)
|
|
dll = C.WinDLL(os.path.join(SDK_DIR, DLL_NAME))
|
|
print("DLL carregada OK:", dll)
|
|
|
|
dll.VT_DeviceScan.argtypes = [C.POINTER(C.c_ubyte), C.c_int]
|
|
dll.VT_DeviceScan.restype = C.c_int
|
|
|
|
dll.VT_DeviceOpen.argtypes = [C.c_void_p, C.POINTER(W.HANDLE), C.c_int, C.c_int]
|
|
dll.VT_DeviceOpen.restype = C.c_int
|
|
|
|
dll.VT_SingleFrameCapture.argtypes = [W.HANDLE, C.POINTER(VT_FRAMEINFO), C.c_int, C.c_int, W.BOOL]
|
|
dll.VT_SingleFrameCapture.restype = C.c_int
|
|
|
|
dll.VT_DeviceClose.argtypes = [C.POINTER(W.HANDLE)]
|
|
dll.VT_DeviceClose.restype = C.c_int
|
|
|
|
# Param API
|
|
dll.VT_ParamGetValue.argtypes = [W.HANDLE, VT_DEVPARAM, C.c_void_p, C.c_int]
|
|
dll.VT_ParamGetValue.restype = C.c_int
|
|
|
|
dll.VT_ParamSetValue.argtypes = [W.HANDLE, VT_DEVPARAM, C.c_void_p, C.c_int]
|
|
dll.VT_ParamSetValue.restype = C.c_int
|
|
|
|
|
|
def ck(ret: int, name: str):
|
|
if ret != 0:
|
|
print(f"{name} falhou, ret={ret}")
|
|
raise RuntimeError(f"{name} falhou, ret={ret}")
|
|
|
|
def param_set_int(h: W.HANDLE, pid: int, value: int):
|
|
p = devparam_by_id(pid)
|
|
v = C.c_int(value)
|
|
ret = dll.VT_ParamSetValue(h, p, C.byref(v), VALUE_INT)
|
|
ck(ret, f"VT_ParamSetValue({hex(pid)})")
|
|
|
|
def param_get_int(h: W.HANDLE, pid: int) -> int:
|
|
p = devparam_by_id(pid)
|
|
v = C.c_int(0)
|
|
ret = dll.VT_ParamGetValue(h, p, C.byref(v), VALUE_INT)
|
|
ck(ret, f"VT_ParamGetValue({hex(pid)})")
|
|
return int(v.value)
|
|
|
|
def param_get_float(h: W.HANDLE, pid: int) -> float:
|
|
p = devparam_by_id(pid)
|
|
v = C.c_float(0)
|
|
ret = dll.VT_ParamGetValue(h, p, C.byref(v), VALUE_FLOAT)
|
|
ck(ret, f"VT_ParamGetValue({hex(pid)})")
|
|
return float(v.value)
|
|
|
|
def set_bool(h: W.HANDLE, pid: int, enabled: bool):
|
|
param_set_int(h, pid, 1 if enabled else 0) # boolean no SDK é int 0/1
|
|
|
|
def param_supported(h, pid, vtype):
|
|
p = devparam_by_id(pid)
|
|
minv = C.c_int()
|
|
maxv = C.c_int()
|
|
inc = C.c_int()
|
|
ret = dll.VT_ParamGetRange(h, p,
|
|
C.byref(minv),
|
|
C.byref(maxv),
|
|
C.byref(inc),
|
|
vtype)
|
|
return ret == 0
|
|
|
|
def capture_raw8(h: W.HANDLE) -> np.ndarray:
|
|
fi = VT_FRAMEINFO()
|
|
ret = dll.VT_SingleFrameCapture(h, C.byref(fi), DATA_RAW, TIMEOUT_MS, True)
|
|
ck(ret, "VT_SingleFrameCapture")
|
|
|
|
w, hh = int(fi.lWidth), int(fi.lHeight)
|
|
if w != RAW_W or hh != RAW_H:
|
|
# Se em algum momento você mudar resolução/ROI, aqui te avisa.
|
|
print(f"[WARN] Res mudou: {w}x{hh} (esperado {RAW_W}x{RAW_H})")
|
|
|
|
buf = C.string_at(fi.pBufPtr, fi.lBufSize)
|
|
arr = np.frombuffer(buf, dtype=np.uint8)
|
|
|
|
# garante reshape correto
|
|
needed = w * hh
|
|
if arr.size < needed:
|
|
arr = np.pad(arr, (0, needed - arr.size), mode="constant", constant_values=0)
|
|
arr = arr[:needed].reshape(hh, w)
|
|
return arr
|
|
|
|
def make_rgb_preview(raw: np.ndarray, upscale=2):
|
|
R = raw[0::2, 0::2]
|
|
G = raw[0::2, 1::2]
|
|
B = raw[1::2, 1::2]
|
|
|
|
# normalização leve só para display (p2-p98)
|
|
def norm8(x):
|
|
lo = np.percentile(x, 2)
|
|
hi = np.percentile(x, 98)
|
|
if hi <= lo + 1:
|
|
return x
|
|
y = (x.astype(np.float32) - lo) * (255.0 / (hi - lo))
|
|
return np.clip(y, 0, 255).astype(np.uint8)
|
|
|
|
Rn, Gn, Bn = map(norm8, [R, G, B])
|
|
|
|
rgb = np.dstack([Bn, Gn, Rn]) # OpenCV = BGR
|
|
if upscale and upscale != 1:
|
|
rgb = cv2.resize(rgb, (rgb.shape[1]*upscale, rgb.shape[0]*upscale), interpolation=cv2.INTER_NEAREST)
|
|
return rgb
|
|
|
|
|
|
def main():
|
|
# scan
|
|
n = C.c_ubyte(0)
|
|
ret = dll.VT_DeviceScan(C.byref(n), DEVICE_UDEF)
|
|
ck(ret, "VT_DeviceScan")
|
|
if n.value == 0:
|
|
raise RuntimeError("Nenhuma câmera encontrada.")
|
|
|
|
# open
|
|
idx = C.c_ubyte(0)
|
|
h = W.HANDLE()
|
|
ret = dll.VT_DeviceOpen(C.byref(idx), C.byref(h), DEVICE_INDEX, DEVICE_UDEF)
|
|
ck(ret, "VT_DeviceOpen")
|
|
print("DeviceOpen OK, handle=", h.value)
|
|
|
|
cv2.namedWindow("RGB", cv2.WINDOW_NORMAL)
|
|
|
|
aec_on = False
|
|
agc_on = False
|
|
exp_raw = 1500
|
|
gain_a = 0
|
|
gain_d = 0
|
|
|
|
has_hw_aec = param_supported(h, PARAM_ID_SENSOR_EXPOSUREAUTOENABLE, VALUE_INT)
|
|
has_hw_agc = param_supported(h, PARAM_ID_SENSOR_GAINANALOGAUTOENABLE, VALUE_INT)
|
|
has_exp_raw = param_supported(h, PARAM_ID_SENSOR_EXPOSURETIMERAW, VALUE_INT)
|
|
has_gain_a = param_supported(h, PARAM_ID_SENSOR_GAINANALOGRAW, VALUE_INT)
|
|
|
|
if (has_hw_aec):
|
|
aec_on = bool(param_get_int(h, PARAM_ID_SENSOR_EXPOSUREAUTOENABLE))
|
|
if (has_hw_agc):
|
|
agc_on = bool(param_get_int(h, PARAM_ID_SENSOR_GAINANALOGAUTOENABLE))
|
|
if (has_exp_raw):
|
|
exp_raw = param_get_int(h, PARAM_ID_SENSOR_EXPOSURETIMERAW)
|
|
if (has_gain_a):
|
|
gain_a = param_get_int(h, PARAM_ID_SENSOR_GAINANALOGRAW)
|
|
|
|
print(f"has_hw_aec: {has_hw_aec}, aec_on: {aec_on}\r\nhas_hw_agc: {has_hw_agc}, agc_on: {agc_on}\r\nhas_exp_raw: {has_exp_raw}, exp_raw: {exp_raw}\r\nhas_gain_a: {has_gain_a}, gain_a: {gain_a}")
|
|
|
|
while True:
|
|
raw = capture_raw8(h)
|
|
rgb = make_rgb_preview(raw, upscale=2)
|
|
cv2.imshow("RGB", rgb)
|
|
|
|
k = cv2.waitKey(1) & 0xFF
|
|
if k in (ord('q'), ord('Q'), 27):
|
|
break
|
|
elif k in (ord('e'), ord('E')): # exemplo: E alterna AEC do hardware
|
|
aec_on = not aec_on
|
|
set_bool(h, PARAM_ID_SENSOR_EXPOSUREAUTOENABLE, aec_on)
|
|
print("AEC(hw) =", aec_on)
|
|
elif k in (ord('g'), ord('G')): # G alterna AGC do hardware
|
|
agc_on = not agc_on
|
|
set_bool(h, PARAM_ID_SENSOR_GAINANALOGAUTOENABLE, agc_on)
|
|
print("AGC(hw) =", agc_on)
|
|
|
|
if __name__ == "__main__":
|
|
main()
|