2026-03-14 22:57:47 +08:00

334 lines
10 KiB
C

/**
* @file qdx_preprocess.c
* @brief Zero-Copy Image Preprocessing Implementation
*/
#include "qdx_preprocess.h"
#include "qdx_port.h"
#include <string.h>
/* ============================================================
* 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 max_payload = out_buffer->TotalCapacity - out_buffer->HeadOffset;
if ((uint32_t)tgt_w * tgt_h * 2u > max_payload) {
uint16_t orig_w = tgt_w, orig_h = tgt_h;
while ((uint32_t)tgt_w * tgt_h * 2u > max_payload && (tgt_w > 1 || tgt_h > 1)) {
tgt_w = (tgt_w + 1) / 2;
tgt_h = (tgt_h + 1) / 2;
}
DBG_PORT("PP: target clamped %dx%d -> %dx%d (buf=%d)\r\n",
(int)orig_w, (int)orig_h, (int)tgt_w, (int)tgt_h, (int)max_payload);
}
uint32_t required_bytes = (uint32_t)tgt_w * tgt_h * 2u;
/* 判断是否需要滑窗计算,或直接导出全图 */
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 */
}