334 lines
10 KiB
C
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 */
|
|
}
|