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
0 commit comments