diff --git a/docs/doc/en/basic/upgrade.md b/docs/doc/en/basic/upgrade.md index 0074d087..5a46b02d 100644 --- a/docs/doc/en/basic/upgrade.md +++ b/docs/doc/en/basic/upgrade.md @@ -41,7 +41,7 @@ Backup methods: | Item | MaixCAM / MaixCAM-Pro | MaixCAM2 | | --------------- | ---------- | ------ | -| Flashing Docs | [MaixCAM System Flashing](https://wiki.sipeed.com/hardware/zh/maixcam/os.html) | [MaixCAM2 System Flashing](https://wiki.sipeed.com/hardware/zh/maixcam/os_maixcam2.html) | +| Flashing Docs | [MaixCAM System Flashing](https://wiki.sipeed.com/hardware/zh/maixcam/os.html) | [MaixCAM2 System Flashing](https://wiki.sipeed.com/hardware/zh/maixcam/maixcam2_os.html) | | System Storage | TF Card | Built-in EMMC (/TF Card) | | TF Card Required | Yes | No | | Flashing Method | USB flashing or card reader flashing | USB flashing or card reader flashing | diff --git a/docs/doc/zh/basic/upgrade.md b/docs/doc/zh/basic/upgrade.md index 6de9ec79..643c4690 100644 --- a/docs/doc/zh/basic/upgrade.md +++ b/docs/doc/zh/basic/upgrade.md @@ -37,7 +37,7 @@ title: MaixCAM MaixPy 升级和烧录系统 | 项目 | MaixCAM / MaixCAM-Pro | MaixCAM2 | | --- | --- | --- | -| 烧录文档 | [MaixCAM 系统烧录](https://wiki.sipeed.com/hardware/zh/maixcam/os.html) | [MaixCAM2 系统烧录](https://wiki.sipeed.com/hardware/zh/maixcam/os_maixcam2.html) | +| 烧录文档 | [MaixCAM 系统烧录](https://wiki.sipeed.com/hardware/zh/maixcam/os.html) | [MaixCAM2 系统烧录](https://wiki.sipeed.com/hardware/zh/maixcam/maixcam2_os.html) | | 系统存放位置 | TF 卡 | 内置EMMC(/TF卡) | | 必须 TF 卡 | 是 | 否 | | 烧录方式 | USB 烧录 或 读卡器烧录 | USB 烧录 或 读卡器烧录 | diff --git a/examples/ext_dev/sensors/imu/acc_gyro_lsm6dsow/lsm6dsow.py b/examples/ext_dev/sensors/imu/acc_gyro_lsm6dsow/lsm6dsow.py new file mode 100644 index 00000000..1b821e6d --- /dev/null +++ b/examples/ext_dev/sensors/imu/acc_gyro_lsm6dsow/lsm6dsow.py @@ -0,0 +1,101 @@ +import smbus2 +import struct +import numpy as np + +class Lsm6dsow: + STATUS_REG = 0x1E + ACC_REG = 0x28 + GYRO_REG = 0x22 + CTRL1_XL = 0x10 # Accelerometer ODR/scale + CTRL2_G = 0x11 # Gyro ODR/scale + + ACC_SENS_TABLE = {0:0.061, 1:0.122, 2:0.244, 3:0.488} + GYRO_SENS_TABLE = {0:8.75, 1:17.5, 2:35, 3:70} + GRAVITY = 9.80665 + ODR_TABLE = [0, 12.5, 26, 52, 104, 208, 416, 833, 1660, 3330, 6660] + + def __init__(self, i2c_bus=1, dev_addr=0x6b): + self.bus = smbus2.SMBus(i2c_bus) + self.addr = dev_addr + self._last_frame = np.zeros((2,3)) # [acc, gyro] last valid + self._last_updated = 0 + + def _read_reg(self, reg, length=1): + if length == 1: + return self.bus.read_byte_data(self.addr, reg) + return self.bus.read_i2c_block_data(self.addr, reg, length) + + def _write_reg(self, reg, value): + self.bus.write_byte_data(self.addr, reg, value) + + def get_acc_odr(self): + val = self._read_reg(self.CTRL1_XL) + odr_idx = (val >> 4) & 0x0F + return self.ODR_TABLE[odr_idx] if odr_idx < len(self.ODR_TABLE) else 0 + + def set_acc_odr(self, hz): + idx = min(range(len(self.ODR_TABLE)), key=lambda i: abs(self.ODR_TABLE[i]-hz)) + val = self._read_reg(self.CTRL1_XL) + val = (val & 0x0F) | (idx << 4) + self._write_reg(self.CTRL1_XL, val) + + def get_gyro_odr(self): + val = self._read_reg(self.CTRL2_G) + odr_idx = (val >> 4) & 0x0F + return self.ODR_TABLE[odr_idx] if odr_idx < len(self.ODR_TABLE) else 0 + + def set_gyro_odr(self, hz): + idx = min(range(len(self.ODR_TABLE)), key=lambda i: abs(self.ODR_TABLE[i]-hz)) + val = self._read_reg(self.CTRL2_G) + val = (val & 0x0F) | (idx << 4) + self._write_reg(self.CTRL2_G, val) + + def get_acc_scale(self): + val = self._read_reg(self.CTRL1_XL) + fs = (val >> 2) & 0x03 + return self.ACC_SENS_TABLE.get(fs, 0.061) + + def set_acc_scale(self, g_val): + fs_map = {2:0, 4:1, 8:2, 16:3} + fs = fs_map.get(g_val, 0) + val = self._read_reg(self.CTRL1_XL) + val = (val & ~(0x0C)) | (fs << 2) + self._write_reg(self.CTRL1_XL, val) + + def get_gyro_scale(self): + val = self._read_reg(self.CTRL2_G) + fs = (val >> 2) & 0x03 + return self.GYRO_SENS_TABLE.get(fs, 8.75) + + def set_gyro_scale(self, dps_val): + fs_map = {250:0, 500:1, 1000:2, 2000:3} + fs = fs_map.get(dps_val, 0) + val = self._read_reg(self.CTRL2_G) + val = (val & ~(0x0C)) | (fs << 2) + self._write_reg(self.CTRL2_G, val) + + def read(self): + status = self._read_reg(self.STATUS_REG) + updated = 0 + if (status & 0x01) and (status & 0x02): + acc_bytes = self._read_reg(self.ACC_REG, 6) + gyro_bytes = self._read_reg(self.GYRO_REG, 6) + acc_raw = np.array(struct.unpack(' 1e-5 else np.array([0,0,1]) + + # 3. 计算旋转后的坐标轴 + R = rotation_matrix_from_vectors(np.array([0,0,1]), acc_norm) + rot_axes = {k: R @ v for k,v in axes.items()} + + # 4. 画三轴 + for k in ['X', 'Y', 'Z']: + tip = project(rot_axes[k]) + cv2.arrowedLine(canvas, tuple(center), tip, colors[k], 3, tipLength=0.10) + cv2.putText(canvas, k, tip, cv2.FONT_HERSHEY_SIMPLEX, 0.7, colors[k], 2) + + # 5. 角速度箭头可视化(方向和长度) + gyro_body = gyro / (np.linalg.norm(gyro)+1e-8) if np.linalg.norm(gyro)>1e-8 else np.array([0,0,1]) + gyro_len = min(float(np.linalg.norm(gyro)), 1.0) * 100 # 可调整箭头长度比例 + # 6. 旋转到加速度系 + gyro_proj = R @ gyro_body + gyro_tip = project(gyro_proj * (gyro_len/150)) # 按长度比例缩放 + + cv2.arrowedLine(canvas, tuple(center), gyro_tip, (0,0,0), 4, tipLength=0.15) + cv2.putText(canvas, "gyro", gyro_tip, cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0,0,0), 2) + + # 7. 显示数值 + cv2.putText(canvas, f"acc: [{acc[0]:.2f}, {acc[1]:.2f}, {acc[2]:.2f}]", (20,60), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0,0,0), 2) + cv2.putText(canvas, f"gyro: [{gyro[0]:.2f}, {gyro[1]:.2f}, {gyro[2]:.2f}]", (20,90), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0,0,0), 2) + + fps = count / (time.time() - start) + cv2.putText(canvas, f"fps: {fps:.2f}", (20,120), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0,255,0), 2) + + img = image.cv2image(canvas) + disp.show(img) + +rate = count / duration +print(f"有效采集速率: {rate:.2f} Hz") + diff --git a/examples/ext_dev/sensors/tiny1c/tiny1c_example.py b/examples/ext_dev/sensors/tiny1c/tiny1c_example.py index db310912..52f8bed3 100644 --- a/examples/ext_dev/sensors/tiny1c/tiny1c_example.py +++ b/examples/ext_dev/sensors/tiny1c/tiny1c_example.py @@ -252,10 +252,10 @@ def scale_to_range(arr, target_min, target_max): if PREVIEW_TEMP: gray = np.frombuffer(image_frame, dtype=np.uint16).reshape((height, width)) + gray = cv2.rotate(gray, cv2.ROTATE_180) # 归一化到8位(0~255) img_8bit = cv2.normalize(gray >> 2, None, 0, 255, cv2.NORM_MINMAX).astype(np.uint8) - img_8bit = cv2.rotate(img_8bit, cv2.ROTATE_180) img_8bit_raw_min_max = (img_8bit.min(), img_8bit.max()) if enable_x3: diff --git a/examples/ext_dev/sensors/tof100/tof100_example.py b/examples/ext_dev/sensors/tof100/tof100_example.py index cd3c7d0e..6a177cfa 100644 --- a/examples/ext_dev/sensors/tof100/tof100_example.py +++ b/examples/ext_dev/sensors/tof100/tof100_example.py @@ -1,19 +1,49 @@ -from maix import display, app, time, ext_dev +from maix import display, image, app, time, ext_dev + +from maix import pinmap + +import numpy as np +import cv2 + +pin_function = { + "A8": "I2C7_SCL", + "A9": "I2C7_SDA", + "B21": "SPI2_CS1", + "B19": "SPI2_MISO", + "B18": "SPI2_MOSI", + "B20": "SPI2_SCK" +} + +for pin, func in pin_function.items(): + if 0 != pinmap.set_pin_function(pin, func): + print(f"Failed: pin{pin}, func{func}") + exit(-1) disp = display.Display() tof = ext_dev.tof100.Tof100( - 4, - ext_dev.tof100.Resolution.RES_50x50, + 2, + ext_dev.tof100.Resolution.RES_100x100, ext_dev.cmap.Cmap.JET, 40, 1000) +t0 = time.time() while not app.need_exit(): img = tof.image() if img is not None: - disp.show(img) - print("min: ", tof.min_dis_point()) - print("max: ", tof.max_dis_point()) - print("center: ", tof.center_point()) + img_bgr = image.image2cv(img, ensure_bgr=True, copy=True) + img_bgr = cv2.rotate(img_bgr, cv2.ROTATE_180) + img_bgr = cv2.resize(img_bgr, (400, 400)) + + scr = np.zeros((disp.height(), disp.width(), 3), dtype=np.uint8) + scr[:img_bgr.shape[0], :img_bgr.shape[1], ...] = img_bgr + disp.show(image.cv2image(scr)) fps = time.fps() - print(f"time: {1000/fps:.02f}ms, fps: {fps:.02f}") \ No newline at end of file + t1 = time.time() + if t1-t0>1: + print("min: ", tof.min_dis_point()) + print("max: ", tof.max_dis_point()) + print("center: ", tof.center_point()) + print(f"time: {1000/fps:.02f}ms, fps: {fps:.02f}") + print(f"t0:{t0}, t1:{t1}") + t0=t1 diff --git a/projects/app_imu_lsm6dsow/.gitignore b/projects/app_imu_lsm6dsow/.gitignore new file mode 100644 index 00000000..3a99d8b5 --- /dev/null +++ b/projects/app_imu_lsm6dsow/.gitignore @@ -0,0 +1,3 @@ +data +__pycache__ +dist diff --git a/projects/app_imu_lsm6dsow/README.md b/projects/app_imu_lsm6dsow/README.md new file mode 100644 index 00000000..415cd60b --- /dev/null +++ b/projects/app_imu_lsm6dsow/README.md @@ -0,0 +1,11 @@ +IMU AHRS 姿态解算 +===== + + +Use integrated IMU to get device status(euler angles). + +APP: [https://maixhub.com/app/128](https://maixhub.com/app/128) + + + + diff --git a/projects/app_imu_lsm6dsow/app.yaml b/projects/app_imu_lsm6dsow/app.yaml new file mode 100644 index 00000000..c86e8eb3 --- /dev/null +++ b/projects/app_imu_lsm6dsow/app.yaml @@ -0,0 +1,18 @@ +id: imu_ahrs +name: IMU AHRS +name[zh]: 姿态解算 +version: 1.0.3 +icon: '' +author: Sipeed Ltd +desc: IMU AHRS +desc[zh]: IMU 姿态解算 +exclude: + - dist + - build + - .gitignore +files: + - assets/icon.png + - app.yaml + - lsm6dsow.py + - main.py + - README.md diff --git a/projects/app_imu_lsm6dsow/assets/icon.png b/projects/app_imu_lsm6dsow/assets/icon.png new file mode 100644 index 00000000..0dd53923 Binary files /dev/null and b/projects/app_imu_lsm6dsow/assets/icon.png differ diff --git a/projects/app_imu_lsm6dsow/lsm6dsow.py b/projects/app_imu_lsm6dsow/lsm6dsow.py new file mode 100644 index 00000000..1b821e6d --- /dev/null +++ b/projects/app_imu_lsm6dsow/lsm6dsow.py @@ -0,0 +1,101 @@ +import smbus2 +import struct +import numpy as np + +class Lsm6dsow: + STATUS_REG = 0x1E + ACC_REG = 0x28 + GYRO_REG = 0x22 + CTRL1_XL = 0x10 # Accelerometer ODR/scale + CTRL2_G = 0x11 # Gyro ODR/scale + + ACC_SENS_TABLE = {0:0.061, 1:0.122, 2:0.244, 3:0.488} + GYRO_SENS_TABLE = {0:8.75, 1:17.5, 2:35, 3:70} + GRAVITY = 9.80665 + ODR_TABLE = [0, 12.5, 26, 52, 104, 208, 416, 833, 1660, 3330, 6660] + + def __init__(self, i2c_bus=1, dev_addr=0x6b): + self.bus = smbus2.SMBus(i2c_bus) + self.addr = dev_addr + self._last_frame = np.zeros((2,3)) # [acc, gyro] last valid + self._last_updated = 0 + + def _read_reg(self, reg, length=1): + if length == 1: + return self.bus.read_byte_data(self.addr, reg) + return self.bus.read_i2c_block_data(self.addr, reg, length) + + def _write_reg(self, reg, value): + self.bus.write_byte_data(self.addr, reg, value) + + def get_acc_odr(self): + val = self._read_reg(self.CTRL1_XL) + odr_idx = (val >> 4) & 0x0F + return self.ODR_TABLE[odr_idx] if odr_idx < len(self.ODR_TABLE) else 0 + + def set_acc_odr(self, hz): + idx = min(range(len(self.ODR_TABLE)), key=lambda i: abs(self.ODR_TABLE[i]-hz)) + val = self._read_reg(self.CTRL1_XL) + val = (val & 0x0F) | (idx << 4) + self._write_reg(self.CTRL1_XL, val) + + def get_gyro_odr(self): + val = self._read_reg(self.CTRL2_G) + odr_idx = (val >> 4) & 0x0F + return self.ODR_TABLE[odr_idx] if odr_idx < len(self.ODR_TABLE) else 0 + + def set_gyro_odr(self, hz): + idx = min(range(len(self.ODR_TABLE)), key=lambda i: abs(self.ODR_TABLE[i]-hz)) + val = self._read_reg(self.CTRL2_G) + val = (val & 0x0F) | (idx << 4) + self._write_reg(self.CTRL2_G, val) + + def get_acc_scale(self): + val = self._read_reg(self.CTRL1_XL) + fs = (val >> 2) & 0x03 + return self.ACC_SENS_TABLE.get(fs, 0.061) + + def set_acc_scale(self, g_val): + fs_map = {2:0, 4:1, 8:2, 16:3} + fs = fs_map.get(g_val, 0) + val = self._read_reg(self.CTRL1_XL) + val = (val & ~(0x0C)) | (fs << 2) + self._write_reg(self.CTRL1_XL, val) + + def get_gyro_scale(self): + val = self._read_reg(self.CTRL2_G) + fs = (val >> 2) & 0x03 + return self.GYRO_SENS_TABLE.get(fs, 8.75) + + def set_gyro_scale(self, dps_val): + fs_map = {250:0, 500:1, 1000:2, 2000:3} + fs = fs_map.get(dps_val, 0) + val = self._read_reg(self.CTRL2_G) + val = (val & ~(0x0C)) | (fs << 2) + self._write_reg(self.CTRL2_G, val) + + def read(self): + status = self._read_reg(self.STATUS_REG) + updated = 0 + if (status & 0x01) and (status & 0x02): + acc_bytes = self._read_reg(self.ACC_REG, 6) + gyro_bytes = self._read_reg(self.GYRO_REG, 6) + acc_raw = np.array(struct.unpack(' 1e-5 else np.array([0,0,1]) + + # 3. 计算旋转后的坐标轴 + R = rotation_matrix_from_vectors(np.array([0,0,1]), acc_norm) + rot_axes = {k: R @ v for k,v in axes.items()} + + # 4. 画三轴 + for k in ['X', 'Y', 'Z']: + tip = project(rot_axes[k]) + cv2.arrowedLine(canvas, tuple(center), tip, colors[k], 3, tipLength=0.10) + cv2.putText(canvas, k, tip, cv2.FONT_HERSHEY_SIMPLEX, 0.7, colors[k], 2) + + # 5. 显示数值 + cv2.putText(canvas, f"acc: [{acc[0]:.2f}, {acc[1]:.2f}, {acc[2]:.2f}]", (20,60), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0,0,255), 2) + cv2.putText(canvas, f"gyro: [{gyro[0]:.2f}, {gyro[1]:.2f}, {gyro[2]:.2f}]", (20,90), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0,0,255), 2) + + fps = count / (time.time() - start) + cv2.putText(canvas, f"fps: {fps:.2f}", (20,120), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0,255,0), 2) + + img = image.cv2image(canvas) + disp.show(img) + +rate = count / duration +print(f"有效采集速率: {rate:.2f} Hz") + diff --git a/projects/app_thermal_tiny1c/app.png b/projects/app_thermal_tiny1c/app.png new file mode 100644 index 00000000..0cd85644 Binary files /dev/null and b/projects/app_thermal_tiny1c/app.png differ diff --git a/projects/app_thermal_tiny1c/app.yaml b/projects/app_thermal_tiny1c/app.yaml new file mode 100644 index 00000000..dc99e36c --- /dev/null +++ b/projects/app_thermal_tiny1c/app.yaml @@ -0,0 +1,18 @@ +id: app_thermal_tiny1c +name: thermal_tiny1c +version: 1.0.0 +author: taorye +icon: app.png +desc: bla bla +files: + - app.png + - app.yaml + - espcn_x2_256x192_u8.axmodel + - espcn_x2.mud + - espcn_x3_256x192_u8.axmodel + - espcn_x3.mud + - main_espcn.py + - main_x3c.py + - main.py + - x3c_192x256.axmodel + - x3c_192x256.mud diff --git a/projects/app_thermal_tiny1c/espcn_x2.mud b/projects/app_thermal_tiny1c/espcn_x2.mud new file mode 100644 index 00000000..9c05ace7 --- /dev/null +++ b/projects/app_thermal_tiny1c/espcn_x2.mud @@ -0,0 +1,8 @@ +[basic] +type = axmodel +model_npu = espcn_x2_256x192_u8.axmodel +model_vnpu = espcn_x2_256x192_u8.axmodel + +[extra] +mean = 0 +scale = 0 \ No newline at end of file diff --git a/projects/app_thermal_tiny1c/espcn_x2_256x192_u8.axmodel b/projects/app_thermal_tiny1c/espcn_x2_256x192_u8.axmodel new file mode 100644 index 00000000..1a8640e8 Binary files /dev/null and b/projects/app_thermal_tiny1c/espcn_x2_256x192_u8.axmodel differ diff --git a/projects/app_thermal_tiny1c/espcn_x3.mud b/projects/app_thermal_tiny1c/espcn_x3.mud new file mode 100644 index 00000000..005d9ab0 --- /dev/null +++ b/projects/app_thermal_tiny1c/espcn_x3.mud @@ -0,0 +1,8 @@ +[basic] +type = axmodel +model_npu = espcn_x3_256x192_u8.axmodel +model_vnpu = espcn_x3_256x192_u8.axmodel + +[extra] +mean = 0 +scale = 0 \ No newline at end of file diff --git a/projects/app_thermal_tiny1c/espcn_x3_256x192_u8.axmodel b/projects/app_thermal_tiny1c/espcn_x3_256x192_u8.axmodel new file mode 100644 index 00000000..3845515f Binary files /dev/null and b/projects/app_thermal_tiny1c/espcn_x3_256x192_u8.axmodel differ diff --git a/projects/app_thermal_tiny1c/main.py b/projects/app_thermal_tiny1c/main.py new file mode 120000 index 00000000..9b8e7dd1 --- /dev/null +++ b/projects/app_thermal_tiny1c/main.py @@ -0,0 +1 @@ +main_x3c.py \ No newline at end of file diff --git a/projects/app_thermal_tiny1c/main_espcn.py b/projects/app_thermal_tiny1c/main_espcn.py new file mode 100644 index 00000000..2634c19e --- /dev/null +++ b/projects/app_thermal_tiny1c/main_espcn.py @@ -0,0 +1,65 @@ +from maix import display, app, image, camera, time, nn, tensor +import numpy as np +import cv2 +import time +import inspect + +width = 256 +height = 192 + +disp = display.Display() +print("display init done") +print(f"display size: {disp.width()}x{disp.height()}") +cam = camera.Camera(disp.width(), disp.height()) # Manually set resolution + # | 手动设置分辨率 + +img = cam.read() +disp.show(img) +factor = 3 +if factor == 2: + xNmodel = nn.NN("./espcn_x2.mud") # 1x192x256x1 -> 1x384x512x1 +elif factor == 3: + xNmodel = nn.NN("./espcn_x3.mud") # 1x192x256x1 -> 1x384x768x1 +else: + raise RuntimeError("Unsupported factor") + +while not app.need_exit(): + scr = np.zeros((disp.height(), disp.width(), 3), dtype=np.uint8) + + # 创建 768x512 的画布 + canvas = np.zeros((height*factor, width*factor*2), dtype=np.uint8) + + img = cam.read() + img_bgr = image.image2cv(img, ensure_bgr=True, copy=True) + img_bgr = cv2.resize(img_bgr, (width, height)) + I_img_np = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2GRAY) + print(I_img_np.shape, I_img_np.dtype) + + # 1. 记录开始时间 + a0 = time.perf_counter() + + I_img = image.cv2image(I_img_np) + xN_tensors = xNmodel.forward_image(I_img) + # print(xN_tensors.keys()) + xN_tensor = xN_tensors['19'] + xN_O_img_np = tensor.tensor_to_numpy_float32(xN_tensor, copy = False) + xN_O_img_np = xN_O_img_np.astype(np.uint8) + xN_O_img_np = xN_O_img_np.reshape((height*factor, width*factor)) + print(xN_O_img_np.shape, xN_O_img_np.dtype) + + xN_I_img_np = cv2.resize(I_img_np, (width*factor, height*factor)) + # print(xN_I_img_np.shape, xN_I_img_np.dtype) + + # 上下拼接 + canvas = np.vstack([xN_I_img_np, xN_O_img_np]) + edge_min = min(disp.height(), disp.width()) + canvas = cv2.resize(canvas, (edge_min, edge_min)) + canvas = cv2.cvtColor(canvas, cv2.COLOR_GRAY2BGR) + + scr[:edge_min, :edge_min, ...] = canvas + + img = image.cv2image(scr) + + print(f"{inspect.currentframe().f_lineno}: 耗时: {time.perf_counter() - a0:.6f} 秒") + + disp.show(img) diff --git a/projects/app_thermal_tiny1c/main_x3c.py b/projects/app_thermal_tiny1c/main_x3c.py new file mode 100644 index 00000000..2edc579b --- /dev/null +++ b/projects/app_thermal_tiny1c/main_x3c.py @@ -0,0 +1,417 @@ +from maix import pinmap +from maix import i2c, spi +from maix import time + +PREVIEW_TEMP = True +FRAME_NUM = 0 +Vtemp = 0 + +pin_function = { + "A8": "I2C7_SCL", + "A9": "I2C7_SDA", + "B21": "SPI2_CS1", + "B19": "SPI2_MISO", + "B18": "SPI2_MOSI", + "B20": "SPI2_SCK" +} + +for pin, func in pin_function.items(): + if 0 != pinmap.set_pin_function(pin, func): + print(f"Failed: pin{pin}, func{func}") + exit(-1) + +############################################################################### +bus = i2c.I2C(7, i2c.Mode.MASTER) +slaves = bus.scan() +print("find slaves:") +for s in slaves: + print(f"{s}[0x{s:02x}]") + +# 0x3c + +# more API see https://wiki.sipeed.com/maixpy/api/maix/peripheral/i2c.html + +############################################################################### +spidev = spi.SPI(2, spi.Mode.MASTER, 30000000, 1, 1, hw_cs=1) + +BUFF_LEN = 4096 +DUMMY_LEN = 512 + +def SPIDataRW(spi_id, tx_data: bytearray, rx_data: bytearray, length: int): + """ + spi_id: 保留参数,未使用 + tx_data: 要发送的数据(bytearray) + rx_data: 用于接收数据(bytearray,长度应为 length) + length: 发送/接收的数据长度 + """ + # 只取前 length 字节发送 + to_send = bytes(tx_data[:length]) + # 调用底层 SPI 读写 + result = spidev.write_read(to_send, length) + # 将结果写入 rx_data + rx_data[:length] = result + +def spi_frame_get(raw_frame: bytearray, frame_byte_size: int, frame_type: int) -> int: + tx_Data = bytearray(BUFF_LEN) + rx_Data = bytearray(BUFF_LEN) + i = 0 + new_frame_cmd = 0 + continue_cmd = 0x55 + + if frame_type == 0: + new_frame_cmd = 0xAA + elif frame_type == 1: + new_frame_cmd = 0xCC + + tx_Data[0] = new_frame_cmd + SPIDataRW(0, tx_Data, rx_Data, BUFF_LEN) + # start from dummy and header + raw_frame[i:i + BUFF_LEN - DUMMY_LEN] = rx_Data[DUMMY_LEN:BUFF_LEN] + i += BUFF_LEN - DUMMY_LEN + + if rx_Data[480] != 1: + return -1 + + global FRAME_NUM + global Vtemp + FRAME_NUM = (rx_Data[480+2] & 0xff) + rx_Data[480+3] * 256 + shutter_state = rx_Data[480+4] + Vtemp = rx_Data[480+5] + rx_Data[480+6] * 256 + gain_state = rx_Data[480+9] + pix_freeze_state = rx_Data[480+12] + print(f"frame num={FRAME_NUM}, shutter={shutter_state}, Vtemp={Vtemp}, gain={gain_state}, freeze={pix_freeze_state}") + + while frame_byte_size - i > BUFF_LEN: + tx_Data[0] = continue_cmd + SPIDataRW(0, tx_Data, rx_Data, BUFF_LEN) + raw_frame[i:i + BUFF_LEN] = rx_Data[:BUFF_LEN] + i += BUFF_LEN + + tx_Data[0] = continue_cmd + SPIDataRW(0, tx_Data, rx_Data, BUFF_LEN) + raw_frame[i:i + (frame_byte_size - i)] = rx_Data[:frame_byte_size - i] + SPIDataRW(0, tx_Data, rx_Data, BUFF_LEN) + return 0 + +############################################################################### +wh = [0x1d, 0x00] +rh = [0x1d, 0x08] +# # d = [0x05, 0x84, 0x04, 0x00, 0x00, 0x04, 0x00, 0x04] # PROJECT_INFO +# # d = [0x05, 0x84, 0x06, 0x00, 0x00, 0x30, 0x00, 0x30] # GET_PN +# d = [0x05, 0x84, 0x07, 0x00, 0x00, 0x10, 0x00, 0x10] # GET_SN +# rl = d[6]*256+d[7] + +# r = bus.writeto(0x3c, bytes(wh+d)) +# print(r) +# r = bus.writeto(0x3c, bytes(rh)) +# print(r) +# time.sleep(1) +# r = bus.readfrom(0x3c, rl) +# print(r) + +# # VCMD_SPI_DEFAULT_RESTORE +# d = [0x01, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00] + +# r = bus.writeto(0x3c, bytes(wh+d)) +# print(r) + +# VCMD_PREVIEW_STOP +print("VCMD_PREVIEW_STOP") +d = [0x0f, 0x02, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00] + +r = bus.writeto(0x3c, bytes(wh+d)) +print(r) +time.sleep(1) + +# VCMD_PREVIEW_START +print("VCMD_PREVIEW_START") +path = 0 # 0x0 : VOC1, 0x1 : VOC2 +src = 0 # 0x0 : IR, 0x80 : fix pattern +width = 256 +height = 192 +fps = 25 +mode = 8 # 0 dvp , 8 spi +d = [0x0f, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, + path, src, width>>8, width&0xff, height>>8, height&0xff, fps, mode] + +r = bus.writeto(0x3c, bytes(wh+d)) +print(r) +time.sleep(5) + +# typedef struct +# { +# uint8_t byCmdType; +# uint8_t bySubCmd; +# uint8_t byPara; +# uint8_t byAddr_h; +# uint8_t byAddr_l; +# uint8_t byAddr_ll; +# uint8_t byLen_h; +# uint8_t byLen_l; +# }vdcmd_std_header_t; + +# VCMD_TEMP_PREVIEW_START +d = [0x0a, 0x01 if PREVIEW_TEMP else 0x02, path, 0x00, 0x00, 0x00, 0x00, 0x00] + +r = bus.writeto(0x3c, bytes(wh+d)) +print(r) +time.sleep(2) + +# print("get_prop_auto_shutter_params------------") +# d = [0x14, 0x82, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00] + [0, 0, 0, 0, 0, 0, 0, 2] + +# r = bus.writeto(0x3c, bytes(wh+d)) +# print(r) +# r = bus.writeto(0x3c, bytes(rh)) +# print(r) +# rl = d[-2]*256+d[-1] +# r = bus.readfrom(0x3c, rl) +# print(r) +# print("get_prop_auto_shutter_params------------") + +if not PREVIEW_TEMP: + # print("pseudo_color_get") + # d = [0x09, 0x84, path, 0x00, 0x00, 0x00, 0x00, 0x01] + + # r = bus.writeto(0x3c, bytes(wh+d)) + # print(r) + # r = bus.writeto(0x3c, bytes(rh)) + # print(r) + # rl = d[6]*256+d[7] + # r = bus.readfrom(0x3c, rl) + # print(r) + # time.sleep(1) + + print("pseudo_color_set") + d = [0x09, 0xc4, path, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01] + + r = bus.writeto(0x3c, bytes(wh+d)) + print(r) + time.sleep(1) + + # print("pseudo_color_get") + # d = [0x09, 0x84, path, 0x00, 0x00, 0x00, 0x00, 0x01] + + # r = bus.writeto(0x3c, bytes(wh+d)) + # print(r) + # r = bus.writeto(0x3c, bytes(rh)) + # print(r) + # rl = d[6]*256+d[7] + # r = bus.readfrom(0x3c, rl) + # print(r) + +############################################################################### +# print("start fetch frame") + +# for i in range(4): +# image_frame = bytearray(width*height*2) +# if spi_frame_get(image_frame, width*height*2, 0) == 0: #src picture +# print("fetched one frame") +# open(f"/root/a{i}.yuv", "wb").write(image_frame) + +# print("stop fetch frame") + +############################################################################### + +def scale_to_range(arr, target_min, target_max): + """ + 将NumPy数组缩放到[target_min, target_max]范围 + + 参数: + arr: 输入的NumPy数组 + target_min: 目标范围的最小值 + target_max: 目标范围的最大值 + + 返回: + 缩放后的NumPy数组 + """ + # 获取原始数组的最小值和最大值 + original_min = arr.min() + original_max = arr.max() + + # 处理数组所有元素相等的特殊情况 + if original_min == original_max: + return np.full_like(arr, target_min) + + # 线性缩放公式 + scaled_arr = (arr - original_min) / (original_max - original_min) * (target_max - target_min) + target_min + return scaled_arr + + +from maix import display, app, image, camera, nn, tensor, touchscreen +import numpy as np +import cv2 +import time +import inspect + +need_exit = 0 +hide_hud = False +enable_x3 = True +ts = touchscreen.TouchScreen() + +disp = display.Display() +print("display init done") +print(f"display size: {disp.width()}x{disp.height()}") +cam = camera.Camera(disp.width(), disp.height()) # Manually set resolution + # | 手动设置分辨率 + +img = cam.read() +disp.show(img) + +x3model_name = './x3c_192x256.mud' +# x3model_name = './espcn_x3.mud' + +x3model = nn.NN(x3model_name) +if x3model_name == './espcn_x3.mud': + output_layer_name = '19' +elif x3model_name == './x3c_192x256.mud': + output_layer_name = 'image_output' +else: + raise RuntimeError("Unsupported x3 model") + +image_frame = bytearray(width*height*2) +while not app.need_exit(): + img = cam.read() + img.draw_string(img.width()//2, img.height()//2, "Error: Init Failed.", image.Color.from_rgb(255, 0, 0)) + gray = np.array([]) + a0 = time.perf_counter() + if spi_frame_get(image_frame, width*height*2, 0) == 0: #src picture + # print(f"{inspect.currentframe().f_lineno}: 耗时: {time.perf_counter() - a0:.6f} 秒") + + # if FRAME_NUM % 50 == 0: + # d = [0x0d, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00] + # r = bus.writeto(0x3c, bytes(wh+d)) + # print(r) + + if PREVIEW_TEMP: + gray = np.frombuffer(image_frame, dtype=np.uint16).reshape((height, width)) + # gray = cv2.rotate(gray, cv2.ROTATE_180) + + # 归一化到8位(0~255) + img_8bit = cv2.normalize(gray, None, 0, 255, cv2.NORM_MINMAX).astype(np.uint8) + img_8bit_raw_min_max = (img_8bit.min(), img_8bit.max()) + + if enable_x3: + grayimg = image.cv2image(img_8bit).resize(256, 192) + x3graytensors = x3model.forward_image(grayimg) + x3graytensor = x3graytensors[output_layer_name] + if x3model_name == './espcn_x3.mud': + x3grayimg_np = tensor.tensor_to_numpy_float32(x3graytensor, copy = False).reshape((576, 768)) + elif x3model_name == './x3c_192x256.mud': + x3grayimg_np = tensor.tensor_to_numpy_uint8(x3graytensor, copy = False).reshape((576, 768)) + else: + raise RuntimeError("Unsupported x3 model") + x3grayimg_np = x3grayimg_np.astype(np.uint8) + + + img_8bit = cv2.resize(x3grayimg_np, (disp.width(), disp.height())) + + img_8bit = scale_to_range(img_8bit, img_8bit_raw_min_max[0], img_8bit_raw_min_max[1]).astype(np.uint8) + else: + img_8bit = cv2.resize(img_8bit, (disp.width(), disp.height())) + + # 伪彩色映射(热力图) + color_img = cv2.applyColorMap(img_8bit, cv2.COLORMAP_MAGMA) # BGR格式 COLORMAP_HOT COLORMAP_TURBO + color_img = image.cv2image(color_img) + + center_pos = (height//2, width//2) + center_temp = gray[center_pos] / 64 - 273.15 + # print(f"c: {center_temp:.3f} ({center_pos})") + color_img.draw_cross(int(color_img.width()*(center_pos[1]/width)), int(color_img.height()*(center_pos[0]/height)), image.COLOR_GREEN, size=8, thickness=3) + color_img.draw_circle(int(color_img.width()*(center_pos[1]/width)), int(color_img.height()*(center_pos[0]/height)), 4, image.COLOR_GREEN, thickness=3) + color_img.draw_string(int(color_img.width()*(center_pos[1]/width)), int(color_img.height()*(center_pos[0]/height)) + 30, f"{center_temp:.1f}", image.COLOR_GREEN, scale=2, thickness=2) + + min_pos, max_pos = np.unravel_index(np.argmin(gray), gray.shape), np.unravel_index(np.argmax(gray), gray.shape) + min_temp, max_temp = (gray[min_pos] / 64 - 273.15), (gray[max_pos] / 64 - 273.15) + + color_img.draw_cross(int(color_img.width()*(max_pos[1]/width)), int(color_img.height()*(max_pos[0]/height)), image.COLOR_WHITE, size=8, thickness=3) + color_img.draw_string(int(color_img.width()*(max_pos[1]/width)), int(color_img.height()*(max_pos[0]/height)) + 30, f"H:{max_temp:.1f}", image.COLOR_WHITE, scale=2, thickness=2) + + color_img.draw_cross(int(color_img.width()*(min_pos[1]/width)), int(color_img.height()*(min_pos[0]/height)), image.COLOR_BLUE, size=8, thickness=3) + color_img.draw_string(int(color_img.width()*(min_pos[1]/width)), int(color_img.height()*(min_pos[0]/height)) + 30, f"L:{min_temp:.1f}", image.COLOR_BLUE, scale=2, thickness=2) + + else: + # # 1. 转为 numpy 数组 + # yuyv = cv2.flip(np.frombuffer(image_frame, dtype=np.uint8).reshape((height, width, 2)), -1) + # # 2. 转为 BGR(OpenCV 默认) + # bgr_img = cv2.cvtColor(yuyv, cv2.COLOR_YUV2BGR_YUYV)[::-1,::-1] + # color_img = bgr_img + # # 3. 提取灰度(Y通道) + # gray = cv2.cvtColor(bgr_img, cv2.COLOR_BGR2GRAY) + + + yuyv = np.frombuffer(image_frame, dtype=np.uint8).reshape((height, width, 2)) + gray = yuyv[::-1,::-1,0] + gray_raw_min_max = (gray.min(), gray.max()) + + if enable_x3: + grayimg = image.cv2image(gray).resize(256, 192) + x3graytensors = x3model.forward_image(grayimg) + x3graytensor = x3graytensors['image_output'] + x3grayimg_np = tensor.tensor_to_numpy_uint8(x3graytensor, copy = False).reshape((576, 768)) + + gray = cv2.resize(x3grayimg_np, (disp.width(), disp.height())) + + gray = scale_to_range(gray, gray_raw_min_max[0], gray_raw_min_max[1]).astype(np.uint8) + else: + gray = cv2.resize(gray, (disp.width(), disp.height())) + + # 4. 伪彩色映射 + color_img = cv2.applyColorMap(gray, cv2.COLORMAP_JET) # BGR 格式 + color_img = image.cv2image(color_img) + + # img.draw_image(0, 0, color_img) + img = color_img + + # print(f"{inspect.currentframe().f_lineno}: 耗时: {time.perf_counter() - a0:.6f} 秒") + + x, y, pressed = ts.read() + if not pressed: + need_exit = 0 + else: + if need_exit or (x >= 0 and x <= 80 and y >= 0 and y <= 40): # Back + need_exit += 1 + else: + if x >= img.width()-120 and x <= img.width() and y >= 0 and y <= 40: # Capture + img_bgr = image.image2cv(img, ensure_bgr=True, copy=True) + img_bgr = cv2.resize(img_bgr, (768, 576) if enable_x3 else (256, 192)) + from datetime import datetime # 用于处理时间戳 + filepath= "/maixapp/share/picture/thermal" + filename = f"thermal({'x3' if enable_x3 else 'raw'})_{datetime.now().strftime("%Y%m%d_%H%M%S")}_{FRAME_NUM}.jpg" + import os + targetfile = os.path.join(filepath, filename) + os.system(f"mkdir -p {filepath}") + + cv2.imwrite(targetfile, img_bgr) + # 强制刷新文件系统缓存(确保数据写入磁盘) + os.fsync(os.open(targetfile, os.O_RDWR)) + img.draw_string(40, img.height()//2, f"Capture to {targetfile}!", image.Color.from_rgb(255, 0, 0), scale=3, thickness=2) + + filename = f"thermal({'x3' if enable_x3 else 'raw'})_{datetime.now().strftime("%Y%m%d_%H%M%S")}_{FRAME_NUM}.npy" + targetfile = os.path.join(filepath, filename) + np.save(targetfile, gray) + os.fsync(os.open(targetfile, os.O_RDWR)) + elif x >= 0 and x <= 120 and y >= img.height()-40 and y <= img.height(): # Lo-Res + enable_x3 = False + elif x >= img.width()-120 and x <= img.width() and y >= img.height()-40 and y <= img.height(): # Hi-Res + enable_x3 = True + elif x >= img.width()//2-60 and x <= img.width()//2+60 and y >= 0 and y <= 40: # Hide HUD + hide_hud = True + elif x >= img.width()//2-60 and x <= img.width()//2+60 and y >= img.height()-40 and y <= img.height(): # Show HUD + hide_hud = False + else: + pass + + if need_exit >= 15: + app.set_exit_flag(True) + + if not hide_hud: + img.draw_string( 10, 10, "Quit(Holding)", image.Color.from_rgb(255, 0 if need_exit == 0 else 255, 0), scale=2, thickness=2) + img.draw_string( img.width()-140, 10, "Capture", image.Color.from_rgb(255, 0, 0), scale=2, thickness=2) + img.draw_string( 10, img.height()-20, "Lo-Res", image.Color.from_rgb(255, 0, 0), scale=2, thickness=2) + img.draw_string( img.width()-120, img.height()-20, "Hi-Res", image.Color.from_rgb(255, 0, 0), scale=2, thickness=2) + img.draw_string(img.width()//2-60, 10, "Hide HUD", image.Color.from_rgb(255, 0, 0), scale=2, thickness=2) + img.draw_string(img.width()//2-60, img.height()-20, "Show HUD", image.Color.from_rgb(255, 0, 0), scale=2, thickness=2) + disp.show(img) + +# /64 - 273.15 \ No newline at end of file diff --git a/projects/app_thermal_tiny1c/x3c_192x256.axmodel b/projects/app_thermal_tiny1c/x3c_192x256.axmodel new file mode 100644 index 00000000..e69de29b diff --git a/projects/app_thermal_tiny1c/x3c_192x256.mud b/projects/app_thermal_tiny1c/x3c_192x256.mud new file mode 100644 index 00000000..968671db --- /dev/null +++ b/projects/app_thermal_tiny1c/x3c_192x256.mud @@ -0,0 +1,8 @@ +[basic] +type = axmodel +model_npu = x3c_192x256.axmodel +model_vnpu = x3c_192x256.axmodel + +[extra] +mean = 0 +scale = 0 \ No newline at end of file diff --git a/projects/app_tof100/app.yaml b/projects/app_tof100/app.yaml new file mode 100644 index 00000000..2633a3f9 --- /dev/null +++ b/projects/app_tof100/app.yaml @@ -0,0 +1,9 @@ +id: app_tof100 +name: tof100 +version: 1.0.0 +author: sipeed +icon: '' +desc: based on example +files: + - app.yaml + - main.py diff --git a/projects/app_tof100/main.py b/projects/app_tof100/main.py new file mode 100644 index 00000000..6a177cfa --- /dev/null +++ b/projects/app_tof100/main.py @@ -0,0 +1,49 @@ +from maix import display, image, app, time, ext_dev + +from maix import pinmap + +import numpy as np +import cv2 + +pin_function = { + "A8": "I2C7_SCL", + "A9": "I2C7_SDA", + "B21": "SPI2_CS1", + "B19": "SPI2_MISO", + "B18": "SPI2_MOSI", + "B20": "SPI2_SCK" +} + +for pin, func in pin_function.items(): + if 0 != pinmap.set_pin_function(pin, func): + print(f"Failed: pin{pin}, func{func}") + exit(-1) + +disp = display.Display() + +tof = ext_dev.tof100.Tof100( + 2, + ext_dev.tof100.Resolution.RES_100x100, + ext_dev.cmap.Cmap.JET, + 40, 1000) + +t0 = time.time() +while not app.need_exit(): + img = tof.image() + if img is not None: + img_bgr = image.image2cv(img, ensure_bgr=True, copy=True) + img_bgr = cv2.rotate(img_bgr, cv2.ROTATE_180) + img_bgr = cv2.resize(img_bgr, (400, 400)) + + scr = np.zeros((disp.height(), disp.width(), 3), dtype=np.uint8) + scr[:img_bgr.shape[0], :img_bgr.shape[1], ...] = img_bgr + disp.show(image.cv2image(scr)) + fps = time.fps() + t1 = time.time() + if t1-t0>1: + print("min: ", tof.min_dis_point()) + print("max: ", tof.max_dis_point()) + print("center: ", tof.center_point()) + print(f"time: {1000/fps:.02f}ms, fps: {fps:.02f}") + print(f"t0:{t0}, t1:{t1}") + t0=t1 diff --git a/projects/build_all.sh b/projects/build_all.sh index fb17ddbb..3a2325b1 100755 --- a/projects/build_all.sh +++ b/projects/build_all.sh @@ -7,8 +7,8 @@ set -e # 定义不同平台的黑名单 blacklist_linux=() -blacklist_maixcam=("app_yoloworld" "app_vlm" "app_mono_depth_estimation" "app_chat" "app_speech") -blacklist_maixcam2=("app_imu_ahrs") +blacklist_maixcam=("app_yoloworld" "app_vlm" "app_mono_depth_estimation" "app_chat" "app_speech" "app_imu_lsm6dsow" "app_tof100" "app_thermal_tiny1c") +blacklist_maixcam2=() #############################################