/** * @file qdx_preprocess.c * @brief Zero-Copy Image Preprocessing Implementation */ #include "qdx_preprocess.h" #include "qdx_port.h" #include /* ============================================================ * Internal State & Configuration Cache * ============================================================ */ /* Static allocation for column sums to avoid malloc */ #define PREPROCESS_MAX_WIDTH 256 static uint32_t g_col_sums[PREPROCESS_MAX_WIDTH]; static uint8_t g_is_initialized = 0; /* 配置读写互斥锁,防止 Execute 与 Settings_Change 并发冲突 */ static qdx_mutex_t g_preprocess_mutex = NULL; static struct { Config2D_t cfg2d; Config1D_t cfg1d; ConfigCommon_t common; } g_PreprocessCfg; /* ============================================================ * API Implementation * ============================================================ */ int8_t Preprocess_Init(uint16_t maxWidth, uint16_t maxHeight) { (void)maxHeight; /* 列累加仅依赖宽度 */ if (maxWidth > PREPROCESS_MAX_WIDTH) { return -1; /* 超出静态分配缓冲区上限 */ } memset(g_col_sums, 0, sizeof(g_col_sums)); memset(&g_PreprocessCfg, 0, sizeof(g_PreprocessCfg)); /* 创建配置互斥锁 */ g_preprocess_mutex = qdx_port_mutex_create(); if (g_preprocess_mutex == NULL) return -1; /* 最小默认配置,防止除零或无限循环 */ g_PreprocessCfg.cfg2d.TargetWidth = 1; g_PreprocessCfg.cfg2d.TargetHeight = 1; g_is_initialized = 1; return 0; } int8_t Preprocess_Settings_Change(const Config2D_t *newConfig2D, const Config1D_t *newConfig1D, const ConfigCommon_t *newCommon) { if (!g_is_initialized) return -1; /* 加锁保护配置更新,防止与 Execute 读取产生竞态 */ qdx_port_mutex_lock(g_preprocess_mutex); if (newConfig2D) memcpy(&g_PreprocessCfg.cfg2d, newConfig2D, sizeof(Config2D_t)); if (newConfig1D) memcpy(&g_PreprocessCfg.cfg1d, newConfig1D, sizeof(Config1D_t)); if (newCommon) memcpy(&g_PreprocessCfg.common, newCommon, sizeof(ConfigCommon_t)); /* 安全检查 */ if (g_PreprocessCfg.cfg2d.TargetWidth == 0) g_PreprocessCfg.cfg2d.TargetWidth = 1; if (g_PreprocessCfg.cfg2d.TargetHeight == 0) g_PreprocessCfg.cfg2d.TargetHeight = 1; qdx_port_mutex_unlock(g_preprocess_mutex); return 0; } int8_t Preprocess_Execute(const RawImageBuffer_t *input, TcpTxBuffer_t *out_buffer, PreprocessResult_t *output_meta) { if (!g_is_initialized || !input || !input->pData || !out_buffer || !out_buffer->pBuffer || !output_meta) return -1; /* 加锁快照当前配置,最小化持锁时间 */ qdx_port_mutex_lock(g_preprocess_mutex); uint16_t tgt_w = g_PreprocessCfg.cfg2d.TargetWidth; uint16_t tgt_h = g_PreprocessCfg.cfg2d.TargetHeight; int16_t thresh = g_PreprocessCfg.cfg2d.TriggerTemperatureThreshold; qdx_port_mutex_unlock(g_preprocess_mutex); uint16_t w = input->Width; uint16_t h = input->Height; /* 目标超过输入时回退到整幅图像 */ if (tgt_w > w) tgt_w = w; if (tgt_h > h) tgt_h = h; /* ---- 缓冲区容量越界检查 ---- */ uint32_t required_bytes = (uint32_t)tgt_w * tgt_h * 2u; if (out_buffer->HeadOffset + required_bytes > out_buffer->TotalCapacity) { return -3; /* 输出缓冲区空间不足 */ } /* 判断是否需要滑窗计算,或直接导出全图 */ if (tgt_w == w && tgt_h == h) { /* 无需滑窗,仅做温度过滤与整体统计 */ int16_t min_t = 32767; int16_t max_t = -32768; uint32_t total_sum = 0; uint32_t pixels = w * h; /* Write directly to out_buffer starting at HeadOffset */ uint8_t *dest_ptr = out_buffer->pBuffer + out_buffer->HeadOffset; for (uint32_t i = 0; i < pixels; i++) { int16_t raww_val = (int16_t)input->pData[i]; if (raww_val < min_t) min_t = raww_val; if (raww_val > max_t) max_t = raww_val; total_sum += raww_val; /* Encode Original Raw Value (Little-Endian sequence into uint8_t) */ *dest_ptr++ = (uint8_t)(raww_val & 0xFF); *dest_ptr++ = (uint8_t)((raww_val >> 8) & 0xFF); } output_meta->pValidData = out_buffer->pBuffer + out_buffer->HeadOffset; output_meta->DataLength = pixels * 2; output_meta->ValidWidth = w; output_meta->ValidHeight = h; output_meta->MinTemp = min_t; output_meta->MaxTemp = max_t; output_meta->AvgTemp = (int16_t)(total_sum / pixels); output_meta->RoiTemp = output_meta->AvgTemp; output_meta->Status = 0; output_meta->FrameNumber = input->FrameNumber; out_buffer->ValidPayloadLen = output_meta->DataLength; return 0; } /* ------------------------------------------------------------------------ Perform Sliding Window Search for Average Temperature Target Region ------------------------------------------------------------------------ */ memset(g_col_sums, 0, w * sizeof(uint32_t)); uint32_t max_region_sum = 0; uint16_t best_x = 0; uint16_t best_y = 0; for (uint16_t y = 0; y <= h - tgt_h; y++) { /* Step 1: Initialize column sums for this row strip If y == 0, we calculate the entire strip. Otherwise, we subtract the top row that left, and add the bottom row that entered. */ if (y == 0) { for (uint16_t c = 0; c < w; c++) { uint32_t col_total = 0; for (uint16_t r = 0; r < tgt_h; r++) { int16_t val = (int16_t)input->pData[r * w + c]; if (val < thresh) val = 90; col_total += val; } g_col_sums[c] = col_total; } } else { /* Slide down by 1 row */ for (uint16_t c = 0; c < w; c++) { int16_t top_val = (int16_t)input->pData[(y - 1) * w + c]; int16_t bot_val = (int16_t)input->pData[(y + tgt_h - 1) * w + c]; if (top_val < thresh) top_val = 90; if (bot_val < thresh) bot_val = 90; g_col_sums[c] = g_col_sums[c] - top_val + bot_val; } } /* Step 2: Slide Across the Columns (Left to Right) */ uint32_t current_window_sum = 0; /* Initialize first window */ for (uint16_t c = 0; c < tgt_w; c++) { current_window_sum += g_col_sums[c]; } if (current_window_sum > max_region_sum) { max_region_sum = current_window_sum; best_x = 0; best_y = y; } /* Slide Right */ for (uint16_t x = 1; x <= w - tgt_w; x++) { current_window_sum = current_window_sum - g_col_sums[x - 1] + g_col_sums[x + tgt_w - 1]; if (current_window_sum > max_region_sum) { max_region_sum = current_window_sum; best_x = x; best_y = y; } } } /* ------------------------------------------------------------------------ Extract the Data ------------------------------------------------------------------------ */ int16_t min_t = 32767; int16_t max_t = -32768; uint32_t total_sum = 0; uint32_t pixels = tgt_w * tgt_h; uint8_t *dest_ptr = out_buffer->pBuffer + out_buffer->HeadOffset; for (uint16_t r = 0; r < tgt_h; r++) { for (uint16_t c = 0; c < tgt_w; c++) { /* Raw offset into original image */ uint32_t src_idx = (best_y + r) * w + (best_x + c); int16_t raww_val = (int16_t)input->pData[src_idx]; if (raww_val < min_t) min_t = raww_val; if (raww_val > max_t) max_t = raww_val; total_sum += raww_val; /* Output Original Raw Values */ *dest_ptr++ = (uint8_t)(raww_val & 0xFF); *dest_ptr++ = (uint8_t)((raww_val >> 8) & 0xFF); } } output_meta->pValidData = out_buffer->pBuffer + out_buffer->HeadOffset; output_meta->DataLength = pixels * 2; output_meta->ValidWidth = tgt_w; output_meta->ValidHeight = tgt_h; output_meta->MinTemp = min_t; output_meta->MaxTemp = max_t; output_meta->AvgTemp = (int16_t)(total_sum / pixels); output_meta->RoiTemp = output_meta->AvgTemp; output_meta->Status = 0; output_meta->FrameNumber = input->FrameNumber; out_buffer->ValidPayloadLen = output_meta->DataLength; return 0; } /* ============================================================ * Internal Trigger Check * ============================================================ */ int8_t Preprocess_CheckInternalTrigger2D(const RawImageBuffer_t *input) { if (!g_is_initialized || !input || !input->pData) return -1; qdx_port_mutex_lock(g_preprocess_mutex); uint16_t roi_x = g_PreprocessCfg.cfg2d.TriggerRoiX; uint16_t roi_y = g_PreprocessCfg.cfg2d.TriggerRoiY; uint16_t roi_w = g_PreprocessCfg.cfg2d.TriggerRoiW; uint16_t roi_h = g_PreprocessCfg.cfg2d.TriggerRoiH; uint8_t condition = g_PreprocessCfg.cfg2d.TriggerCondition; int16_t thresh = g_PreprocessCfg.cfg2d.TriggerTemperatureThreshold; qdx_port_mutex_unlock(g_preprocess_mutex); uint16_t w = input->Width; uint16_t h = input->Height; /* Boundary Check & Clipping */ if (roi_w == 0 || roi_h == 0) return 0; if (roi_x >= w || roi_y >= h) return 0; if (roi_x + roi_w > w) roi_w = w - roi_x; if (roi_y + roi_h > h) roi_h = h - roi_y; int16_t max_temp = -32768; int64_t sum_temp = 0; uint32_t count = roi_w * roi_h; for (uint16_t r = 0; r < roi_h; r++) { for (uint16_t c = 0; c < roi_w; c++) { int16_t val = (int16_t)input->pData[(roi_y + r) * w + (roi_x + c)]; /* Temperature Filtration Preprocessing */ if (val < thresh) { val = 90; /* Treat as 9.0C */ } if (val > max_temp) max_temp = val; sum_temp += val; } } int16_t calc_val = 0; if (condition == 1) { /* 1: Max */ calc_val = max_temp; } else { /* 0: Average */ calc_val = (int16_t)(sum_temp / count); } if (calc_val >= thresh) { return 1; /* Triggered */ } return 0; /* Not triggered */ }