Skip to content

Commit a00467c

Browse files
authored
Merge pull request #151 from taorye/dev
feat(tiny1c): Add example script and empty model files for thermal se…
2 parents f497b06 + b067f5b commit a00467c

File tree

3 files changed

+379
-0
lines changed

3 files changed

+379
-0
lines changed
Lines changed: 371 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,371 @@
1+
from maix import pinmap
2+
from maix import i2c, spi
3+
from maix import time
4+
5+
PREVIEW_TEMP = True
6+
FRAME_NUM = 0
7+
8+
pin_function = {
9+
"A8": "I2C7_SCL",
10+
"A9": "I2C7_SDA",
11+
"B21": "SPI2_CS1",
12+
"B19": "SPI2_MISO",
13+
"B18": "SPI2_MOSI",
14+
"B20": "SPI2_SCK"
15+
}
16+
17+
for pin, func in pin_function.items():
18+
if 0 != pinmap.set_pin_function(pin, func):
19+
print(f"Failed: pin{pin}, func{func}")
20+
exit(-1)
21+
22+
###############################################################################
23+
bus = i2c.I2C(7, i2c.Mode.MASTER)
24+
slaves = bus.scan()
25+
print("find slaves:")
26+
for s in slaves:
27+
print(f"{s}[0x{s:02x}]")
28+
29+
# 0x3c
30+
31+
# more API see https://wiki.sipeed.com/maixpy/api/maix/peripheral/i2c.html
32+
33+
###############################################################################
34+
spidev = spi.SPI(2, spi.Mode.MASTER, 20000000, 1, 1, hw_cs=1)
35+
36+
BUFF_LEN = 4096
37+
DUMMY_LEN = 512
38+
39+
def SPIDataRW(spi_id, tx_data: bytearray, rx_data: bytearray, length: int):
40+
"""
41+
spi_id: 保留参数,未使用
42+
tx_data: 要发送的数据(bytearray)
43+
rx_data: 用于接收数据(bytearray,长度应为 length)
44+
length: 发送/接收的数据长度
45+
"""
46+
# 只取前 length 字节发送
47+
to_send = bytes(tx_data[:length])
48+
# 调用底层 SPI 读写
49+
result = spidev.write_read(to_send, length)
50+
# 将结果写入 rx_data
51+
rx_data[:length] = result
52+
53+
def spi_frame_get(raw_frame: bytearray, frame_byte_size: int, frame_type: int) -> int:
54+
tx_Data = bytearray(BUFF_LEN)
55+
rx_Data = bytearray(BUFF_LEN)
56+
i = 0
57+
new_frame_cmd = 0
58+
continue_cmd = 0x55
59+
60+
if frame_type == 0:
61+
new_frame_cmd = 0xAA
62+
elif frame_type == 1:
63+
new_frame_cmd = 0xCC
64+
65+
tx_Data[0] = new_frame_cmd
66+
SPIDataRW(0, tx_Data, rx_Data, BUFF_LEN)
67+
# start from dummy and header
68+
raw_frame[i:i + BUFF_LEN - DUMMY_LEN] = rx_Data[DUMMY_LEN:BUFF_LEN]
69+
i += BUFF_LEN - DUMMY_LEN
70+
71+
if rx_Data[480] != 1:
72+
return -1
73+
74+
global FRAME_NUM
75+
FRAME_NUM = (rx_Data[480+2] & 0xff) + rx_Data[480+3] * 256
76+
print(f"frame num={FRAME_NUM}")
77+
78+
while frame_byte_size - i > BUFF_LEN:
79+
tx_Data[0] = continue_cmd
80+
SPIDataRW(0, tx_Data, rx_Data, BUFF_LEN)
81+
raw_frame[i:i + BUFF_LEN] = rx_Data[:BUFF_LEN]
82+
i += BUFF_LEN
83+
84+
tx_Data[0] = continue_cmd
85+
SPIDataRW(0, tx_Data, rx_Data, BUFF_LEN)
86+
raw_frame[i:i + (frame_byte_size - i)] = rx_Data[:frame_byte_size - i]
87+
SPIDataRW(0, tx_Data, rx_Data, BUFF_LEN)
88+
return 0
89+
90+
###############################################################################
91+
wh = [0x1d, 0x00]
92+
rh = [0x1d, 0x08]
93+
# # d = [0x05, 0x84, 0x04, 0x00, 0x00, 0x04, 0x00, 0x04] # PROJECT_INFO
94+
# # d = [0x05, 0x84, 0x06, 0x00, 0x00, 0x30, 0x00, 0x30] # GET_PN
95+
# d = [0x05, 0x84, 0x07, 0x00, 0x00, 0x10, 0x00, 0x10] # GET_SN
96+
# rl = d[6]*256+d[7]
97+
98+
# r = bus.writeto(0x3c, bytes(wh+d))
99+
# print(r)
100+
# r = bus.writeto(0x3c, bytes(rh))
101+
# print(r)
102+
# time.sleep(1)
103+
# r = bus.readfrom(0x3c, rl)
104+
# print(r)
105+
106+
# # VCMD_SPI_DEFAULT_RESTORE
107+
# d = [0x01, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
108+
109+
# r = bus.writeto(0x3c, bytes(wh+d))
110+
# print(r)
111+
112+
# VCMD_PREVIEW_STOP
113+
print("VCMD_PREVIEW_STOP")
114+
d = [0x0f, 0x02, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00]
115+
116+
r = bus.writeto(0x3c, bytes(wh+d))
117+
print(r)
118+
time.sleep(1)
119+
120+
# VCMD_PREVIEW_START
121+
print("VCMD_PREVIEW_START")
122+
path = 0 # 0x0 : VOC1, 0x1 : VOC2
123+
src = 0 # 0x0 : IR, 0x80 : fix pattern
124+
width = 256
125+
height = 192
126+
fps = 25
127+
mode = 8 # 0 dvp , 8 spi
128+
d = [0x0f, 0xc1, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00,
129+
path, src, width>>8, width&0xff, height>>8, height&0xff, fps, mode]
130+
131+
r = bus.writeto(0x3c, bytes(wh+d))
132+
print(r)
133+
time.sleep(1)
134+
135+
# typedef struct
136+
# {
137+
# uint8_t byCmdType;
138+
# uint8_t bySubCmd;
139+
# uint8_t byPara;
140+
# uint8_t byAddr_h;
141+
# uint8_t byAddr_l;
142+
# uint8_t byAddr_ll;
143+
# uint8_t byLen_h;
144+
# uint8_t byLen_l;
145+
# }vdcmd_std_header_t;
146+
147+
# VCMD_TEMP_PREVIEW_START
148+
d = [0x0a, 0x01 if PREVIEW_TEMP else 0x02, path, 0x00, 0x00, 0x00, 0x00, 0x00]
149+
150+
r = bus.writeto(0x3c, bytes(wh+d))
151+
print(r)
152+
time.sleep(2)
153+
154+
155+
if not PREVIEW_TEMP:
156+
# print("pseudo_color_get")
157+
# d = [0x09, 0x84, path, 0x00, 0x00, 0x00, 0x00, 0x01]
158+
159+
# r = bus.writeto(0x3c, bytes(wh+d))
160+
# print(r)
161+
# r = bus.writeto(0x3c, bytes(rh))
162+
# print(r)
163+
# rl = d[6]*256+d[7]
164+
# r = bus.readfrom(0x3c, rl)
165+
# print(r)
166+
# time.sleep(1)
167+
168+
print("pseudo_color_set")
169+
d = [0x09, 0xc4, path, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01]
170+
171+
r = bus.writeto(0x3c, bytes(wh+d))
172+
print(r)
173+
time.sleep(1)
174+
175+
# print("pseudo_color_get")
176+
# d = [0x09, 0x84, path, 0x00, 0x00, 0x00, 0x00, 0x01]
177+
178+
# r = bus.writeto(0x3c, bytes(wh+d))
179+
# print(r)
180+
# r = bus.writeto(0x3c, bytes(rh))
181+
# print(r)
182+
# rl = d[6]*256+d[7]
183+
# r = bus.readfrom(0x3c, rl)
184+
# print(r)
185+
186+
###############################################################################
187+
# print("start fetch frame")
188+
189+
# for i in range(4):
190+
# image_frame = bytearray(width*height*2)
191+
# if spi_frame_get(image_frame, width*height*2, 0) == 0: #src picture
192+
# print("fetched one frame")
193+
# open(f"/root/a{i}.yuv", "wb").write(image_frame)
194+
195+
# print("stop fetch frame")
196+
197+
###############################################################################
198+
199+
def scale_to_range(arr, target_min, target_max):
200+
"""
201+
将NumPy数组缩放到[target_min, target_max]范围
202+
203+
参数:
204+
arr: 输入的NumPy数组
205+
target_min: 目标范围的最小值
206+
target_max: 目标范围的最大值
207+
208+
返回:
209+
缩放后的NumPy数组
210+
"""
211+
# 获取原始数组的最小值和最大值
212+
original_min = arr.min()
213+
original_max = arr.max()
214+
215+
# 处理数组所有元素相等的特殊情况
216+
if original_min == original_max:
217+
return np.full_like(arr, target_min)
218+
219+
# 线性缩放公式
220+
scaled_arr = (arr - original_min) / (original_max - original_min) * (target_max - target_min) + target_min
221+
return scaled_arr
222+
223+
224+
from maix import display, app, image, camera, nn, tensor, touchscreen
225+
import numpy as np
226+
import cv2
227+
import time
228+
import inspect
229+
230+
need_exit = 0
231+
hide_hud = False
232+
enable_x3 = True
233+
ts = touchscreen.TouchScreen()
234+
235+
disp = display.Display()
236+
print("display init done")
237+
print(f"display size: {disp.width()}x{disp.height()}")
238+
cam = camera.Camera(disp.width(), disp.height()) # Manually set resolution
239+
# | 手动设置分辨率
240+
241+
img = cam.read()
242+
disp.show(img)
243+
x3model = nn.NN("./x3c_192x256.mud")
244+
245+
image_frame = bytearray(width*height*2)
246+
while not app.need_exit():
247+
img = cam.read()
248+
img.draw_string(img.width()//2, img.height()//2, "Error: Init Failed.", image.Color.from_rgb(255, 0, 0))
249+
a0 = time.perf_counter()
250+
if spi_frame_get(image_frame, width*height*2, 0) == 0: #src picture
251+
print(f"{inspect.currentframe().f_lineno}: 耗时: {time.perf_counter() - a0:.6f} 秒")
252+
253+
if PREVIEW_TEMP:
254+
gray = np.frombuffer(image_frame, dtype=np.uint16).reshape((height, width))
255+
256+
# 归一化到8位(0~255)
257+
img_8bit = cv2.normalize(gray >> 2, None, 0, 255, cv2.NORM_MINMAX).astype(np.uint8)
258+
img_8bit_raw_min_max = (img_8bit.min(), img_8bit.max())
259+
260+
if enable_x3:
261+
grayimg = image.cv2image(img_8bit).resize(256, 192)
262+
x3graytensors = x3model.forward_image(grayimg)
263+
x3graytensor = x3graytensors['image_output']
264+
x3grayimg_np = tensor.tensor_to_numpy_uint8(x3graytensor, copy = False).reshape((576, 768))
265+
266+
img_8bit = cv2.resize(x3grayimg_np, (disp.width(), disp.height()))
267+
268+
img_8bit = scale_to_range(img_8bit, img_8bit_raw_min_max[0], img_8bit_raw_min_max[1]).astype(np.uint8)
269+
else:
270+
img_8bit = cv2.resize(img_8bit, (disp.width(), disp.height()))
271+
272+
# 伪彩色映射(热力图)
273+
color_img = cv2.applyColorMap(img_8bit, cv2.COLORMAP_MAGMA) # BGR格式 COLORMAP_HOT COLORMAP_TURBO
274+
color_img = image.cv2image(color_img)
275+
276+
center_pos = (height//2, width//2)
277+
center_temp = gray[center_pos] / 64 - 273.15
278+
# print(f"c: {center_temp:.3f} ({center_pos})")
279+
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)
280+
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)
281+
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)
282+
283+
min_pos, max_pos = np.unravel_index(np.argmin(gray), gray.shape), np.unravel_index(np.argmax(gray), gray.shape)
284+
min_temp, max_temp = (gray[min_pos] / 64 - 273.15), (gray[max_pos] / 64 - 273.15)
285+
286+
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)
287+
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)
288+
289+
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)
290+
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)
291+
292+
else:
293+
# # 1. 转为 numpy 数组
294+
# yuyv = cv2.flip(np.frombuffer(image_frame, dtype=np.uint8).reshape((height, width, 2)), -1)
295+
# # 2. 转为 BGR(OpenCV 默认)
296+
# bgr_img = cv2.cvtColor(yuyv, cv2.COLOR_YUV2BGR_YUYV)[::-1,::-1]
297+
# color_img = bgr_img
298+
# # 3. 提取灰度(Y通道)
299+
# gray = cv2.cvtColor(bgr_img, cv2.COLOR_BGR2GRAY)
300+
301+
302+
yuyv = np.frombuffer(image_frame, dtype=np.uint8).reshape((height, width, 2))
303+
gray = yuyv[:,:,0]
304+
gray_raw_min_max = (gray.min(), gray.max())
305+
306+
if enable_x3:
307+
grayimg = image.cv2image(gray).resize(256, 192)
308+
x3graytensors = x3model.forward_image(grayimg)
309+
x3graytensor = x3graytensors['image_output']
310+
x3grayimg_np = tensor.tensor_to_numpy_uint8(x3graytensor, copy = False).reshape((576, 768))
311+
312+
gray = cv2.resize(x3grayimg_np, (disp.width(), disp.height()))
313+
314+
gray = scale_to_range(gray, gray_raw_min_max[0], gray_raw_min_max[1]).astype(np.uint8)
315+
else:
316+
gray = cv2.resize(gray, (disp.width(), disp.height()))
317+
318+
# 4. 伪彩色映射
319+
color_img = cv2.applyColorMap(gray, cv2.COLORMAP_JET) # BGR 格式
320+
color_img = image.cv2image(color_img)
321+
322+
# img.draw_image(0, 0, color_img)
323+
img = color_img
324+
325+
print(f"{inspect.currentframe().f_lineno}: 耗时: {time.perf_counter() - a0:.6f} 秒")
326+
327+
x, y, pressed = ts.read()
328+
if not pressed:
329+
need_exit = 0
330+
else:
331+
if need_exit or (x >= 0 and x <= 80 and y >= 0 and y <= 40): # Back
332+
need_exit += 1
333+
else:
334+
if x >= img.width()-120 and x <= img.width() and y >= 0 and y <= 40: # Capture
335+
img_bgr = image.image2cv(img, ensure_bgr=True, copy=True)
336+
img_bgr = cv2.resize(img_bgr, (768, 576) if enable_x3 else (256, 192))
337+
from datetime import datetime # 用于处理时间戳
338+
filepath= "/maixapp/share/picture/thermal"
339+
filename = f"thermal({'x3' if enable_x3 else 'raw'})_{datetime.now().strftime("%Y%m%d_%H%M%S")}_{FRAME_NUM}.jpg"
340+
import os
341+
targetfile = os.path.join(filepath, filename)
342+
os.system(f"mkdir -p {filepath}")
343+
344+
cv2.imwrite(targetfile, img_bgr)
345+
# 强制刷新文件系统缓存(确保数据写入磁盘)
346+
os.fsync(os.open(targetfile, os.O_RDWR))
347+
img.draw_string(40, img.height()//2, f"Capture to {targetfile}!", image.Color.from_rgb(255, 0, 0), scale=3, thickness=2)
348+
elif x >= 0 and x <= 120 and y >= img.height()-40 and y <= img.height(): # Lo-Res
349+
enable_x3 = False
350+
elif x >= img.width()-120 and x <= img.width() and y >= img.height()-40 and y <= img.height(): # Hi-Res
351+
enable_x3 = True
352+
elif x >= img.width()//2-60 and x <= img.width()//2+60 and y >= 0 and y <= 40: # Hide HUD
353+
hide_hud = True
354+
elif x >= img.width()//2-60 and x <= img.width()//2+60 and y >= img.height()-40 and y <= img.height(): # Show HUD
355+
hide_hud = False
356+
else:
357+
pass
358+
359+
if need_exit >= 15:
360+
app.set_exit_flag(True)
361+
362+
if not hide_hud:
363+
img.draw_string( 10, 10, "Back", image.Color.from_rgb(255, 0 if need_exit == 0 else 255, 0), scale=2, thickness=2)
364+
img.draw_string( img.width()-140, 10, "Capture", image.Color.from_rgb(255, 0, 0), scale=2, thickness=2)
365+
img.draw_string( 10, img.height()-20, "Lo-Res", image.Color.from_rgb(255, 0, 0), scale=2, thickness=2)
366+
img.draw_string( img.width()-120, img.height()-20, "Hi-Res", image.Color.from_rgb(255, 0, 0), scale=2, thickness=2)
367+
img.draw_string(img.width()//2-60, 10, "Hide HUD", image.Color.from_rgb(255, 0, 0), scale=2, thickness=2)
368+
img.draw_string(img.width()//2-60, img.height()-20, "Show HUD", image.Color.from_rgb(255, 0, 0), scale=2, thickness=2)
369+
disp.show(img)
370+
371+
# /64 - 273.15

examples/ext_dev/sensors/tiny1c/x3c_192x256.axmodel

Whitespace-only changes.
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
[basic]
2+
type = axmodel
3+
model_npu = x3c_192x256.axmodel
4+
model_vnpu = x3c_192x256.axmodel
5+
6+
[extra]
7+
mean = 0
8+
scale = 0

0 commit comments

Comments
 (0)