You've already forked MicroPythonOS
mirror of
https://github.com/m5stack/MicroPythonOS.git
synced 2026-05-20 11:51:27 -07:00
Merge branch 'webcam-works'
This commit is contained in:
+28
-20
@@ -13,11 +13,12 @@
|
||||
|
||||
#define WIDTH 640
|
||||
#define HEIGHT 480
|
||||
#define NUM_BUFFERS 4
|
||||
#define NUM_BUFFERS 1 # more buffers doesnt seem to help so one is enough
|
||||
#define OUTPUT_WIDTH 240
|
||||
#define OUTPUT_HEIGHT 240
|
||||
|
||||
// Forward declaration of the webcam type
|
||||
#define WEBCAM_DEBUG_PRINT(...) mp_printf(&mp_plat_print, __VA_ARGS__)
|
||||
|
||||
static const mp_obj_type_t webcam_type;
|
||||
|
||||
typedef struct _webcam_obj_t {
|
||||
@@ -26,19 +27,26 @@ typedef struct _webcam_obj_t {
|
||||
void *buffers[NUM_BUFFERS];
|
||||
size_t buffer_length;
|
||||
int frame_count;
|
||||
unsigned char *gray_buffer; // Persistent buffer for memoryview
|
||||
unsigned char *gray_buffer;
|
||||
} webcam_obj_t;
|
||||
|
||||
static void yuyv_to_grayscale_240x240(unsigned char *yuyv, unsigned char *gray, int in_width, int in_height) {
|
||||
float x_ratio = (float)in_width / OUTPUT_WIDTH;
|
||||
float y_ratio = (float)in_height / OUTPUT_HEIGHT;
|
||||
// Crop to 480x480 centered region
|
||||
int crop_size = 480;
|
||||
int crop_x_offset = (in_width - crop_size) / 2; // Center the crop: (640 - 480) / 2 = 80
|
||||
int crop_y_offset = (in_height - crop_size) / 2; // Center the crop: (480 - 480) / 2 = 0
|
||||
|
||||
// Downscale ratios from 480x480 to 240x240
|
||||
float x_ratio = (float)crop_size / OUTPUT_WIDTH; // 480 / 240 = 2.0
|
||||
float y_ratio = (float)crop_size / OUTPUT_HEIGHT; // 480 / 240 = 2.0
|
||||
|
||||
for (int y = 0; y < OUTPUT_HEIGHT; y++) {
|
||||
for (int x = 0; x < OUTPUT_WIDTH; x++) {
|
||||
int src_x = (int)(x * x_ratio);
|
||||
int src_y = (int)(y * y_ratio);
|
||||
int src_index = (src_y * in_width + src_x) * 2;
|
||||
gray[y * OUTPUT_WIDTH + x] = yuyv[src_index];
|
||||
// Map output pixel to cropped region
|
||||
int src_x = (int)(x * x_ratio) + crop_x_offset; // Adjust for crop offset
|
||||
int src_y = (int)(y * y_ratio) + crop_y_offset; // Adjust for crop offset
|
||||
int src_index = (src_y * in_width + src_x) * 2; // YUYV uses 2 bytes per pixel
|
||||
gray[y * OUTPUT_WIDTH + x] = yuyv[src_index]; // Extract Y channel
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -46,7 +54,7 @@ static void yuyv_to_grayscale_240x240(unsigned char *yuyv, unsigned char *gray,
|
||||
static void save_raw(const char *filename, unsigned char *data, int width, int height) {
|
||||
FILE *fp = fopen(filename, "wb");
|
||||
if (!fp) {
|
||||
fprintf(stderr, "Cannot open file %s: %s\n", filename, strerror(errno));
|
||||
WEBCAM_DEBUG_PRINT("Cannot open file %s: %s\n", filename, strerror(errno));
|
||||
return;
|
||||
}
|
||||
fwrite(data, 1, width * height, fp);
|
||||
@@ -56,7 +64,7 @@ static void save_raw(const char *filename, unsigned char *data, int width, int h
|
||||
static int init_webcam(webcam_obj_t *self, const char *device) {
|
||||
self->fd = open(device, O_RDWR);
|
||||
if (self->fd < 0) {
|
||||
fprintf(stderr, "Cannot open device: %s\n", strerror(errno));
|
||||
WEBCAM_DEBUG_PRINT("Cannot open device: %s\n", strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -67,7 +75,7 @@ static int init_webcam(webcam_obj_t *self, const char *device) {
|
||||
fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;
|
||||
fmt.fmt.pix.field = V4L2_FIELD_ANY;
|
||||
if (ioctl(self->fd, VIDIOC_S_FMT, &fmt) < 0) {
|
||||
fprintf(stderr, "Cannot set format: %s\n", strerror(errno));
|
||||
WEBCAM_DEBUG_PRINT("Cannot set format: %s\n", strerror(errno));
|
||||
close(self->fd);
|
||||
return -1;
|
||||
}
|
||||
@@ -77,7 +85,7 @@ static int init_webcam(webcam_obj_t *self, const char *device) {
|
||||
req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
req.memory = V4L2_MEMORY_MMAP;
|
||||
if (ioctl(self->fd, VIDIOC_REQBUFS, &req) < 0) {
|
||||
fprintf(stderr, "Cannot request buffers: %s\n", strerror(errno));
|
||||
WEBCAM_DEBUG_PRINT("Cannot request buffers: %s\n", strerror(errno));
|
||||
close(self->fd);
|
||||
return -1;
|
||||
}
|
||||
@@ -88,14 +96,14 @@ static int init_webcam(webcam_obj_t *self, const char *device) {
|
||||
buf.memory = V4L2_MEMORY_MMAP;
|
||||
buf.index = i;
|
||||
if (ioctl(self->fd, VIDIOC_QUERYBUF, &buf) < 0) {
|
||||
fprintf(stderr, "Cannot query buffer: %s\n", strerror(errno));
|
||||
WEBCAM_DEBUG_PRINT("Cannot query buffer: %s\n", strerror(errno));
|
||||
close(self->fd);
|
||||
return -1;
|
||||
}
|
||||
self->buffer_length = buf.length;
|
||||
self->buffers[i] = mmap(NULL, buf.length, PROT_READ | PROT_WRITE, MAP_SHARED, self->fd, buf.m.offset);
|
||||
if (self->buffers[i] == MAP_FAILED) {
|
||||
fprintf(stderr, "Cannot map buffer: %s\n", strerror(errno));
|
||||
WEBCAM_DEBUG_PRINT("Cannot map buffer: %s\n", strerror(errno));
|
||||
close(self->fd);
|
||||
return -1;
|
||||
}
|
||||
@@ -107,21 +115,21 @@ static int init_webcam(webcam_obj_t *self, const char *device) {
|
||||
buf.memory = V4L2_MEMORY_MMAP;
|
||||
buf.index = i;
|
||||
if (ioctl(self->fd, VIDIOC_QBUF, &buf) < 0) {
|
||||
fprintf(stderr, "Cannot queue buffer: %s\n", strerror(errno));
|
||||
WEBCAM_DEBUG_PRINT("Cannot queue buffer: %s\n", strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
if (ioctl(self->fd, VIDIOC_STREAMON, &type) < 0) {
|
||||
fprintf(stderr, "Cannot start streaming: %s\n", strerror(errno));
|
||||
WEBCAM_DEBUG_PRINT("Cannot start streaming: %s\n", strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
self->frame_count = 0;
|
||||
self->gray_buffer = (unsigned char *)malloc(OUTPUT_WIDTH * OUTPUT_HEIGHT);
|
||||
if (!self->gray_buffer) {
|
||||
fprintf(stderr, "Cannot allocate gray buffer: %s\n", strerror(errno));
|
||||
WEBCAM_DEBUG_PRINT("Cannot allocate gray buffer: %s\n", strerror(errno));
|
||||
close(self->fd);
|
||||
return -1;
|
||||
}
|
||||
@@ -178,7 +186,7 @@ static mp_obj_t capture_frame(webcam_obj_t *self) {
|
||||
// snprintf(filename, sizeof(filename), "frame_%03d.raw", self->frame_count++);
|
||||
// save_raw(filename, self->gray_buffer, OUTPUT_WIDTH, OUTPUT_HEIGHT);
|
||||
|
||||
mp_obj_t result = mp_obj_new_bytes(self->gray_buffer, OUTPUT_WIDTH * OUTPUT_HEIGHT);
|
||||
mp_obj_t result = mp_obj_new_memoryview('b', OUTPUT_WIDTH * OUTPUT_HEIGHT, self->gray_buffer);
|
||||
|
||||
if (ioctl(self->fd, VIDIOC_QBUF, &buf) < 0) {
|
||||
mp_raise_OSError(MP_EIO);
|
||||
@@ -189,7 +197,7 @@ static mp_obj_t capture_frame(webcam_obj_t *self) {
|
||||
|
||||
static mp_obj_t webcam_init(size_t n_args, const mp_obj_t *args) {
|
||||
mp_arg_check_num(n_args, 0, 0, 1, false);
|
||||
const char *device = "/dev/video0"; // Default device
|
||||
const char *device = "/dev/video0";
|
||||
if (n_args == 1) {
|
||||
device = mp_obj_str_get_str(args[0]);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
width=240
|
||||
height=240
|
||||
|
||||
import webcam
|
||||
import time
|
||||
|
||||
cam = webcam.init("/dev/video0") # Initialize webcam with device path
|
||||
memview = webcam.capture_frame(cam) # Returns memoryview
|
||||
time.sleep_ms(1000)
|
||||
static_bytes_obj = bytes(memview)
|
||||
|
||||
|
||||
image = lv.image(lv.screen_active())
|
||||
image.align(lv.ALIGN.LEFT_MID, 0, 0)
|
||||
image.set_rotation(900)
|
||||
# Create image descriptor once
|
||||
image_dsc = lv.image_dsc_t({
|
||||
"header": {
|
||||
"magic": lv.IMAGE_HEADER_MAGIC,
|
||||
"w": width,
|
||||
"h": height,
|
||||
"stride": width ,
|
||||
"cf": lv.COLOR_FORMAT.L8
|
||||
},
|
||||
'data_size': width * height,
|
||||
'data': static_bytes_obj # Will be updated per frame
|
||||
})
|
||||
image.set_src(image_dsc)
|
||||
|
||||
for i in range(300):
|
||||
print(f"iteration {i}")
|
||||
webcam.recapture_frame(cam) #refresh memview
|
||||
bytes_obj = bytes(memview)
|
||||
#print(f"got bytes: {len(bytes_obj)}")
|
||||
#image_dsc.data = static_bytes_obj
|
||||
image_dsc.data = bytes_obj
|
||||
#image.set_src(image_dsc)
|
||||
image.invalidate()
|
||||
time.sleep_ms(10) # seems to need more than 0 or 1 ms
|
||||
|
||||
print("cleanup")
|
||||
webcam.deinit(cam) # Deinitializes webcam
|
||||
@@ -602,7 +602,7 @@ def log_callback(level, log_str):
|
||||
# Convert log_str to string if it's a bytes object
|
||||
log_str = log_str.decode() if isinstance(log_str, bytes) else log_str
|
||||
# Optional: Print for debugging
|
||||
print(f"Level: {level}, Log: {log_str}")
|
||||
#print(f"Level: {level}, Log: {log_str}")
|
||||
# Log message format: "sysmon: 25 FPS (refr_cnt: 8 | redraw_cnt: 1), ..."
|
||||
if "sysmon:" in log_str and "FPS" in log_str:
|
||||
try:
|
||||
@@ -628,14 +628,18 @@ def get_fps():
|
||||
|
||||
|
||||
# Main loop
|
||||
for _ in range(10):
|
||||
import time
|
||||
fps = get_fps()
|
||||
if fps > 0: # Only print when FPS is updated
|
||||
print("Current FPS:", fps)
|
||||
time.sleep(1)
|
||||
def print_fps():
|
||||
for _ in range(100):
|
||||
import time
|
||||
fps = get_fps()
|
||||
if fps > 0: # Only print when FPS is updated
|
||||
print("Current FPS:", fps)
|
||||
time.sleep(1)
|
||||
|
||||
|
||||
import _thread
|
||||
_thread.stack_size(12*1024)
|
||||
_thread.start_new_thread(print_fps, ())
|
||||
|
||||
# crash:
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import time
|
||||
import webcam
|
||||
|
||||
appscreen = lv.screen_active()
|
||||
th.disable()
|
||||
|
||||
keepgoing = True
|
||||
keepliveqrdecoding = False
|
||||
@@ -15,6 +16,7 @@ image = None
|
||||
qr_label = None
|
||||
use_webcam = False
|
||||
|
||||
memview = None
|
||||
|
||||
def print_qr_buffer(buffer):
|
||||
try:
|
||||
@@ -59,9 +61,12 @@ def close_button_click(e):
|
||||
|
||||
def snap_button_click(e):
|
||||
print("Picture taken!")
|
||||
import os
|
||||
try:
|
||||
import os
|
||||
os.mkdir("data")
|
||||
except OSError:
|
||||
pass
|
||||
try:
|
||||
os.mkdir("data/com.example.camtest")
|
||||
except OSError:
|
||||
pass
|
||||
@@ -93,89 +98,24 @@ def qr_button_click(e):
|
||||
qr_label.set_text(lv.SYMBOL.EYE_OPEN)
|
||||
|
||||
def try_capture():
|
||||
global current_cam_buffer, image_dsc, image
|
||||
use_webcam = True # Set to True for webcam module
|
||||
if use_webcam:
|
||||
new_cam_buffer = None
|
||||
new_cam_buffer = webcam.capture_frame(cam) # Returns bytes
|
||||
else:
|
||||
new_cam_buffer = cam.capture() # Returns memoryview for other camera
|
||||
if new_cam_buffer and len(new_cam_buffer) == 240 * 240:
|
||||
if current_cam_buffer is not None:
|
||||
if use_webcam:
|
||||
#pass
|
||||
webcam.free_buffer(cam) # Explicitly free webcam buffer
|
||||
else:
|
||||
cam.free_buffer() # Free other camera buffer
|
||||
else:
|
||||
pass
|
||||
print("current_cam_buffer is None, not freeing...")
|
||||
current_cam_buffer = None
|
||||
current_cam_buffer = new_cam_buffer # Store new buffer reference
|
||||
# Update image descriptor with new buffer
|
||||
# Set image source to update LVGL
|
||||
#image_dsc.data = None # this doesnt help
|
||||
#image.set_src(None) #this crashes it
|
||||
oldsrc = image.get_src()
|
||||
if oldsrc:
|
||||
oldsrc.delete()
|
||||
#image_dsc.data = current_cam_buffer
|
||||
image_dsc = lv.image_dsc_t({
|
||||
"header": {
|
||||
"magic": lv.IMAGE_HEADER_MAGIC,
|
||||
"w": width,
|
||||
"h": height,
|
||||
"stride": width ,
|
||||
#"cf": lv.COLOR_FORMAT.RGB565
|
||||
"cf": lv.COLOR_FORMAT.L8
|
||||
},
|
||||
'data_size': width * height,
|
||||
'data': current_cam_buffer # Will be updated per frame
|
||||
})
|
||||
#image.invalidate()
|
||||
image.set_src(image_dsc)
|
||||
else:
|
||||
print("Invalid buffer size:", len(new_cam_buffer))
|
||||
if use_webcam:
|
||||
pass
|
||||
#webcam.free_buffer(cam)
|
||||
else:
|
||||
cam.free_buffer()
|
||||
|
||||
def try_capture_again():
|
||||
global current_cam_buffer, image_dsc, image
|
||||
new_cam_buffer = webcam.capture_frame(cam) # Returns memoryview
|
||||
if new_cam_buffer and len(new_cam_buffer) == 240 * 240:
|
||||
image_dsc.data = new_cam_buffer # Update image descriptor
|
||||
image.set_src(image_dsc) # Update LVGL image
|
||||
current_cam_buffer = None # Clear reference to allow GC
|
||||
else:
|
||||
print("Invalid buffer size:", len(new_cam_buffer))
|
||||
|
||||
def try_capture_old():
|
||||
global current_cam_buffer, image_dsc, image, use_webcam
|
||||
if use_webcam:
|
||||
new_cam_buffer = webcam.capture_frame(cam)
|
||||
current_cam_buffer = webcam.capture_frame(cam)
|
||||
elif cam.frame_available():
|
||||
new_cam_buffer = cam.capture() # Returns memoryview
|
||||
if new_cam_buffer and len(new_cam_buffer):
|
||||
# print("Invalid buffer size:", len(new_cam_buffer))
|
||||
# cam.free_buffer()
|
||||
# return
|
||||
# Update image descriptor with new memoryview
|
||||
image_dsc.data = new_cam_buffer
|
||||
# Set image source to update LVGL (implicitly invalidates widget)
|
||||
current_cam_buffer = cam.capture() # Returns memoryview
|
||||
if current_cam_buffer and len(current_cam_buffer):
|
||||
image_dsc.data = current_cam_buffer
|
||||
#image.invalidate() # does not work so do this:
|
||||
image.set_src(image_dsc)
|
||||
#current_cam_buffer = None # Clear reference to allow GC
|
||||
#image.invalidate() #does not work
|
||||
# Free the previous buffer (if any) after setting new data
|
||||
if current_cam_buffer is not None and not use_webcam:
|
||||
if not use_webcam:
|
||||
cam.free_buffer() # Free the old buffer
|
||||
current_cam_buffer = new_cam_buffer # Store new buffer reference
|
||||
else:
|
||||
print("No image received from camera, ignoring...")
|
||||
return
|
||||
|
||||
|
||||
def build_ui():
|
||||
global image, image_dsc,qr_label
|
||||
global image, image_dsc,qr_label, cam
|
||||
cont = lv.obj(appscreen)
|
||||
cont.set_style_pad_all(0, 0)
|
||||
cont.set_style_border_width(0, 0)
|
||||
@@ -205,7 +145,8 @@ def build_ui():
|
||||
# Initialize LVGL image widget
|
||||
image = lv.image(cont)
|
||||
image.align(lv.ALIGN.LEFT_MID, 0, 0)
|
||||
image.set_rotation(900)
|
||||
if not use_webcam:
|
||||
image.set_rotation(900)
|
||||
# Create image descriptor once
|
||||
image_dsc = lv.image_dsc_t({
|
||||
"header": {
|
||||
@@ -217,14 +158,13 @@ def build_ui():
|
||||
"cf": lv.COLOR_FORMAT.L8
|
||||
},
|
||||
'data_size': width * height,
|
||||
'data': None # Will be updated per frame
|
||||
'data': None # Will be updated per frame
|
||||
})
|
||||
image.set_src(image_dsc)
|
||||
|
||||
|
||||
def init_cam():
|
||||
try:
|
||||
# time.sleep(1) doesn't help
|
||||
from camera import Camera, GrabMode, PixelFormat, FrameSize, GainCeiling
|
||||
cam = Camera(
|
||||
data_pins=[12,13,15,11,14,10,7,2],
|
||||
@@ -260,22 +200,23 @@ def init_cam():
|
||||
|
||||
cam = init_cam()
|
||||
if not cam:
|
||||
print("init cam failed, retrying with webcam...")
|
||||
print("camtest.py: no internal camera found, trying webcam on /dev/video0")
|
||||
try:
|
||||
cam = webcam.init("/dev/video0") # Initialize webcam with device path
|
||||
use_webcam = True
|
||||
except Exception as e:
|
||||
print(f"camtest.py: webcam exception: {e}")
|
||||
|
||||
if cam or use_webcam:
|
||||
if cam:
|
||||
build_ui()
|
||||
count=0
|
||||
while appscreen == lv.screen_active() and keepgoing is True:
|
||||
print(f"capture nr {count}")
|
||||
count += 1
|
||||
try_capture()
|
||||
time.sleep_ms(100) # Allow for the MicroPython REPL to still work. Reducing it doesn't seem to affect the on-display FPS.
|
||||
print("App backgrounded, deinitializing camera...")
|
||||
# Task handler needs to be updated from the same thread, otherwise it causes concurrency issues:
|
||||
lv.task_handler()
|
||||
time.sleep_ms(1)
|
||||
lv.tick_inc(1)
|
||||
print("camtest.py: stopping...")
|
||||
if use_webcam:
|
||||
webcam.deinit(cam) # Deinitializes webcam
|
||||
else:
|
||||
@@ -283,6 +224,7 @@ if cam or use_webcam:
|
||||
else:
|
||||
print("No camera found, exiting...")
|
||||
|
||||
th.enable()
|
||||
show_launcher()
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"name": "CamTestNew",
|
||||
"publisher": "ACME Inc",
|
||||
"short_description": "Simple test of the camera",
|
||||
"long_description": "A simple test of the camera makes it possible to validate the hardware.",
|
||||
"icon_url": "http://demo.lnpiggy.com:2121/apps/com.example.camtest_0.0.2.mpk_icon_64x64.png",
|
||||
"download_url": "http://demo.lnpiggy.com:2121/apps/com.example.camtest_0.0.2.mpk",
|
||||
"fullname": "com.example.camtest",
|
||||
"version": "0.0.2",
|
||||
"entrypoint": "assets/camtestnew.py",
|
||||
"category": "camera"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,288 @@
|
||||
import time
|
||||
import webcam
|
||||
|
||||
appscreen = lv.screen_active()
|
||||
|
||||
keepgoing = True
|
||||
keepliveqrdecoding = False
|
||||
width = 240
|
||||
height = 240
|
||||
|
||||
# Variable to hold the current memoryview to prevent garbage collection
|
||||
current_cam_buffer = None
|
||||
image_dsc = None
|
||||
image = None
|
||||
qr_label = None
|
||||
use_webcam = False
|
||||
|
||||
|
||||
def print_qr_buffer(buffer):
|
||||
try:
|
||||
# Try to decode buffer as a UTF-8 string
|
||||
result = buffer.decode('utf-8')
|
||||
# Check if the string is printable (ASCII printable characters)
|
||||
if all(32 <= ord(c) <= 126 for c in result):
|
||||
return result
|
||||
except Exception as e:
|
||||
pass
|
||||
# If not a valid string or not printable, convert to hex
|
||||
hex_str = ' '.join([f'{b:02x}' for b in buffer])
|
||||
return hex_str.lower()
|
||||
|
||||
# Byte-Order-Mark is added sometimes
|
||||
def remove_bom(buffer):
|
||||
bom = b'\xEF\xBB\xBF'
|
||||
if buffer.startswith(bom):
|
||||
return buffer[3:]
|
||||
return buffer
|
||||
|
||||
def qrdecode_live():
|
||||
# Image dimensions
|
||||
buffer_size = width * height # 240 * 240 = 57600 bytes
|
||||
while keepgoing and keepliveqrdecoding:
|
||||
try:
|
||||
import qrdecode
|
||||
result = qrdecode.qrdecode(current_cam_buffer, width, height)
|
||||
result = remove_bom(result)
|
||||
result = print_qr_buffer(result)
|
||||
print(f"QR decoding found: {result}")
|
||||
except Exception as e:
|
||||
print("QR decode error: ", e)
|
||||
time.sleep_ms(500)
|
||||
|
||||
|
||||
def close_button_click(e):
|
||||
global keepgoing
|
||||
print("Close button clicked")
|
||||
keepgoing = False
|
||||
|
||||
|
||||
def snap_button_click(e):
|
||||
print("Picture taken!")
|
||||
try:
|
||||
import os
|
||||
os.mkdir("data")
|
||||
os.mkdir("data/com.example.camtest")
|
||||
except OSError:
|
||||
pass
|
||||
if current_cam_buffer is not None:
|
||||
filename="data/com.example.camtest/capture.raw"
|
||||
try:
|
||||
with open(filename, 'wb') as f:
|
||||
f.write(current_cam_buffer)
|
||||
print(f"Successfully wrote current_cam_buffer to {filename}")
|
||||
except OSError as e:
|
||||
print(f"Error writing to file: {e}")
|
||||
|
||||
|
||||
def qr_button_click(e):
|
||||
global keepliveqrdecoding, qr_label
|
||||
if not keepliveqrdecoding:
|
||||
print("Activating live QR decoding...")
|
||||
keepliveqrdecoding = True
|
||||
qr_label.set_text(lv.SYMBOL.EYE_CLOSE)
|
||||
try:
|
||||
import _thread
|
||||
_thread.stack_size(12*1024) # 16KB is too much
|
||||
_thread.start_new_thread(qrdecode_live, ())
|
||||
except Exception as e:
|
||||
print("Could not start live QR decoding thread: ", e)
|
||||
else:
|
||||
print("Deactivating live QR decoding...")
|
||||
keepliveqrdecoding = False
|
||||
qr_label.set_text(lv.SYMBOL.EYE_OPEN)
|
||||
|
||||
def try_capture():
|
||||
global current_cam_buffer, image_dsc, image
|
||||
use_webcam = True # Set to True for webcam module
|
||||
if use_webcam:
|
||||
new_cam_buffer = None
|
||||
new_cam_buffer = webcam.capture_frame(cam) # Returns bytes
|
||||
else:
|
||||
new_cam_buffer = cam.capture() # Returns memoryview for other camera
|
||||
if new_cam_buffer and len(new_cam_buffer) == 240 * 240:
|
||||
if current_cam_buffer is not None:
|
||||
if use_webcam:
|
||||
#pass
|
||||
webcam.free_buffer(cam) # Explicitly free webcam buffer
|
||||
else:
|
||||
cam.free_buffer() # Free other camera buffer
|
||||
else:
|
||||
pass
|
||||
print("current_cam_buffer is None, not freeing...")
|
||||
current_cam_buffer = None
|
||||
current_cam_buffer = new_cam_buffer # Store new buffer reference
|
||||
# Update image descriptor with new buffer
|
||||
# Set image source to update LVGL
|
||||
#image_dsc.data = None # this doesnt help
|
||||
#image.set_src(None) #this crashes it
|
||||
oldsrc = image.get_src()
|
||||
if oldsrc:
|
||||
oldsrc.delete()
|
||||
#image_dsc.data = current_cam_buffer
|
||||
image_dsc = lv.image_dsc_t({
|
||||
"header": {
|
||||
"magic": lv.IMAGE_HEADER_MAGIC,
|
||||
"w": width,
|
||||
"h": height,
|
||||
"stride": width ,
|
||||
#"cf": lv.COLOR_FORMAT.RGB565
|
||||
"cf": lv.COLOR_FORMAT.L8
|
||||
},
|
||||
'data_size': width * height,
|
||||
'data': current_cam_buffer # Will be updated per frame
|
||||
})
|
||||
#image.invalidate()
|
||||
image.set_src(image_dsc)
|
||||
else:
|
||||
print("Invalid buffer size:", len(new_cam_buffer))
|
||||
if use_webcam:
|
||||
pass
|
||||
#webcam.free_buffer(cam)
|
||||
else:
|
||||
cam.free_buffer()
|
||||
|
||||
def try_capture_again():
|
||||
global current_cam_buffer, image_dsc, image
|
||||
new_cam_buffer = webcam.capture_frame(cam) # Returns memoryview
|
||||
if new_cam_buffer and len(new_cam_buffer) == 240 * 240:
|
||||
image_dsc.data = new_cam_buffer # Update image descriptor
|
||||
image.set_src(image_dsc) # Update LVGL image
|
||||
current_cam_buffer = None # Clear reference to allow GC
|
||||
else:
|
||||
print("Invalid buffer size:", len(new_cam_buffer))
|
||||
|
||||
def try_capture_old():
|
||||
global current_cam_buffer, image_dsc, image, use_webcam
|
||||
if use_webcam:
|
||||
new_cam_buffer = webcam.capture_frame(cam)
|
||||
elif cam.frame_available():
|
||||
new_cam_buffer = cam.capture() # Returns memoryview
|
||||
if new_cam_buffer and len(new_cam_buffer):
|
||||
# print("Invalid buffer size:", len(new_cam_buffer))
|
||||
# cam.free_buffer()
|
||||
# return
|
||||
# Update image descriptor with new memoryview
|
||||
image_dsc.data = new_cam_buffer
|
||||
# Set image source to update LVGL (implicitly invalidates widget)
|
||||
image.set_src(image_dsc)
|
||||
#current_cam_buffer = None # Clear reference to allow GC
|
||||
#image.invalidate() #does not work
|
||||
# Free the previous buffer (if any) after setting new data
|
||||
if current_cam_buffer is not None and not use_webcam:
|
||||
cam.free_buffer() # Free the old buffer
|
||||
current_cam_buffer = new_cam_buffer # Store new buffer reference
|
||||
|
||||
|
||||
def build_ui():
|
||||
global image, image_dsc,qr_label
|
||||
cont = lv.obj(appscreen)
|
||||
cont.set_style_pad_all(0, 0)
|
||||
cont.set_style_border_width(0, 0)
|
||||
cont.set_size(lv.pct(100), lv.pct(100))
|
||||
cont.set_scrollbar_mode(lv.SCROLLBAR_MODE.OFF)
|
||||
close_button = lv.button(cont)
|
||||
close_button.set_size(60,60)
|
||||
close_button.align(lv.ALIGN.TOP_RIGHT, 0, 0)
|
||||
close_label = lv.label(close_button)
|
||||
close_label.set_text(lv.SYMBOL.CLOSE)
|
||||
close_label.center()
|
||||
close_button.add_event_cb(close_button_click,lv.EVENT.CLICKED,None)
|
||||
snap_button = lv.button(cont)
|
||||
snap_button.set_size(60, 60)
|
||||
snap_button.align(lv.ALIGN.RIGHT_MID, 0, 0)
|
||||
snap_label = lv.label(snap_button)
|
||||
snap_label.set_text(lv.SYMBOL.OK)
|
||||
snap_label.center()
|
||||
snap_button.add_event_cb(snap_button_click,lv.EVENT.CLICKED,None)
|
||||
qr_button = lv.button(cont)
|
||||
qr_button.set_size(60, 60)
|
||||
qr_button.align(lv.ALIGN.BOTTOM_RIGHT, 0, 0)
|
||||
qr_label = lv.label(qr_button)
|
||||
qr_label.set_text(lv.SYMBOL.EYE_OPEN)
|
||||
qr_label.center()
|
||||
qr_button.add_event_cb(qr_button_click,lv.EVENT.CLICKED,None)
|
||||
# Initialize LVGL image widget
|
||||
image = lv.image(cont)
|
||||
image.align(lv.ALIGN.LEFT_MID, 0, 0)
|
||||
image.set_rotation(900)
|
||||
# Create image descriptor once
|
||||
image_dsc = lv.image_dsc_t({
|
||||
"header": {
|
||||
"magic": lv.IMAGE_HEADER_MAGIC,
|
||||
"w": width,
|
||||
"h": height,
|
||||
"stride": width ,
|
||||
#"cf": lv.COLOR_FORMAT.RGB565
|
||||
"cf": lv.COLOR_FORMAT.L8
|
||||
},
|
||||
'data_size': width * height,
|
||||
'data': None # Will be updated per frame
|
||||
})
|
||||
image.set_src(image_dsc)
|
||||
|
||||
|
||||
def init_cam():
|
||||
try:
|
||||
# time.sleep(1) doesn't help
|
||||
from camera import Camera, GrabMode, PixelFormat, FrameSize, GainCeiling
|
||||
cam = Camera(
|
||||
data_pins=[12,13,15,11,14,10,7,2],
|
||||
vsync_pin=6,
|
||||
href_pin=4,
|
||||
sda_pin=21,
|
||||
scl_pin=16,
|
||||
pclk_pin=9,
|
||||
xclk_pin=8,
|
||||
xclk_freq=20000000,
|
||||
powerdown_pin=-1,
|
||||
reset_pin=-1,
|
||||
#pixel_format=PixelFormat.RGB565,
|
||||
pixel_format=PixelFormat.GRAYSCALE,
|
||||
frame_size=FrameSize.R240X240,
|
||||
grab_mode=GrabMode.LATEST
|
||||
)
|
||||
#cam.init() automatically done when creating the Camera()
|
||||
#cam.reconfigure(frame_size=FrameSize.HVGA)
|
||||
#frame_size=FrameSize.HVGA, # 480x320
|
||||
#frame_size=FrameSize.QVGA, # 320x240
|
||||
#frame_size=FrameSize.QQVGA # 160x120
|
||||
cam.set_vflip(True)
|
||||
return cam
|
||||
except Exception as e:
|
||||
print(f"init_cam exception: {e}")
|
||||
return None
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
cam = init_cam()
|
||||
if not cam:
|
||||
print("init cam failed, retrying with webcam...")
|
||||
try:
|
||||
cam = webcam.init("/dev/video0") # Initialize webcam with device path
|
||||
use_webcam = True
|
||||
except Exception as e:
|
||||
print(f"camtest.py: webcam exception: {e}")
|
||||
|
||||
if cam or use_webcam:
|
||||
build_ui()
|
||||
count=0
|
||||
while appscreen == lv.screen_active() and keepgoing is True:
|
||||
print(f"capture nr {count}")
|
||||
count += 1
|
||||
try_capture()
|
||||
time.sleep_ms(100) # Allow for the MicroPython REPL to still work. Reducing it doesn't seem to affect the on-display FPS.
|
||||
print("App backgrounded, deinitializing camera...")
|
||||
if use_webcam:
|
||||
webcam.deinit(cam) # Deinitializes webcam
|
||||
else:
|
||||
cam.deinit()
|
||||
else:
|
||||
print("No camera found, exiting...")
|
||||
|
||||
show_launcher()
|
||||
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
# This works with copy-paste
|
||||
# It also works like this:
|
||||
# import mpos
|
||||
# mpos.apps.execute_script("apps/com.example.camtestnew/assets/camtestnew.py", True, False, True)
|
||||
# import mpos
|
||||
# mpos.apps.execute_script_new_thread("apps/com.example.camtestnew/assets/camtestnew.py", True, False, True)
|
||||
|
||||
|
||||
width=240
|
||||
height=240
|
||||
|
||||
import webcam
|
||||
import time
|
||||
|
||||
th.disable()
|
||||
|
||||
cam = webcam.init("/dev/video0") # Initialize webcam with device path
|
||||
#memview = None
|
||||
#memview = webcam.capture_frame(cam) # Returns memoryview
|
||||
#time.sleep_ms(1000)
|
||||
#static_bytes_obj = bytes(memview)
|
||||
|
||||
|
||||
image = lv.image(lv.screen_active())
|
||||
image.align(lv.ALIGN.LEFT_MID, 0, 0)
|
||||
#image.set_rotation(900)
|
||||
# Create image descriptor once
|
||||
image_dsc = lv.image_dsc_t({
|
||||
"header": {
|
||||
"magic": lv.IMAGE_HEADER_MAGIC,
|
||||
"w": width,
|
||||
"h": height,
|
||||
"stride": width ,
|
||||
"cf": lv.COLOR_FORMAT.L8
|
||||
},
|
||||
'data_size': width * height,
|
||||
'data': None # Will be updated per frame
|
||||
})
|
||||
image.set_src(image_dsc)
|
||||
|
||||
#memview = webcam.capture_frame(cam) # Returns memoryview
|
||||
|
||||
for i in range(100):
|
||||
print(f"iteration {i}")
|
||||
webcam.recapture_frame(cam) #refresh memview
|
||||
#memview =
|
||||
#bytes_obj = bytes(memview)
|
||||
#print(f"got bytes: {len(bytes_obj)}")
|
||||
#image_dsc.data = bytes_obj
|
||||
#image_dsc.data = static_bytes_obj
|
||||
image_dsc.data = webcam.capture_frame(cam) # Returns memoryview
|
||||
image.set_src(image_dsc)
|
||||
#image.invalidate()
|
||||
lv.task_handler()
|
||||
time.sleep_ms(5) # seems to need more than 0 or 1 ms, otherwise there's almost never a new image...
|
||||
lv.tick_inc(5)
|
||||
|
||||
print("cleanup")
|
||||
webcam.deinit(cam) # Deinitializes webcam
|
||||
|
||||
|
||||
th.enable()
|
||||
@@ -35,6 +35,7 @@ def execute_script(script_source, is_file, is_launcher, is_graphical):
|
||||
lv.screen_load(newscreen)
|
||||
script_globals = {
|
||||
'lv': lv,
|
||||
'th': mpos.ui.th,
|
||||
'NOTIFICATION_BAR_HEIGHT': mpos.ui.NOTIFICATION_BAR_HEIGHT, # for apps that want to leave space for notification bar
|
||||
'appscreen': newscreen,
|
||||
'start_app': start_app, # for launcher apps
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import lvgl as lv
|
||||
import mpos.apps
|
||||
|
||||
th = None
|
||||
|
||||
NOTIFICATION_BAR_HEIGHT=24
|
||||
|
||||
CLOCK_UPDATE_INTERVAL = 1000 # 10 or even 1 ms doesn't seem to change the framerate but 100ms is enough
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import task_handler
|
||||
th = task_handler.TaskHandler(duration=5) # 5ms is recommended for MicroPython+LVGL on desktop
|
||||
|
||||
|
||||
import mpos.ui
|
||||
mpos.ui.create_rootscreen()
|
||||
mpos.ui.create_notification_bar()
|
||||
mpos.ui.create_drawer(display)
|
||||
mpos.ui.th = task_handler.TaskHandler(duration=5) # 5ms is recommended for MicroPython+LVGL on desktop
|
||||
|
||||
try:
|
||||
import freezefs_mount_builtin
|
||||
|
||||
Reference in New Issue
Block a user