@brief: preprocess the image for Qwen3VL model @note: Converts uint8 image to BF16 format, data is already in (3, H, W) CHW layout @param: image: the image to preprocess (already in CHW format) @return: the preprocessed image in BF16 format
| 159 | ///@param: image: the image to preprocess (already in CHW format) |
| 160 | ///@return: the preprocessed image in BF16 format |
| 161 | void Qwen3VL::preprocess_image(qwen3vl_image_t& image, std::vector<bf16> &pixel_values) { |
| 162 | const int width = image.width; |
| 163 | const int height = image.height; |
| 164 | const int channels = 3; // RGB |
| 165 | int resized_height; |
| 166 | int resized_width; |
| 167 | // do the automatically resizing in here |
| 168 | smart_resize( |
| 169 | height, width, |
| 170 | resized_height, resized_width, |
| 171 | QWEN3_PATCH_SIZE * QWEN3_IMAGE_MERGE_SIZE, |
| 172 | QWEN3_SHORTEST_EDGE, |
| 173 | QWEN3_LONGEST_EDGE |
| 174 | ); |
| 175 | // std::cout << "resized_height "<< resized_height << " resized_width " << resized_width <<std::endl; |
| 176 | |
| 177 | // Cache size calculations for efficiency |
| 178 | const uint32_t single_frame_size = resized_height * resized_width * channels; |
| 179 | const uint32_t total_patch_size = single_frame_size * QWEN3_TEMPORAL_PATCH_SIZE; |
| 180 | const uint32_t grid_h = resized_height / QWEN3_PATCH_SIZE; |
| 181 | const uint32_t grid_w = resized_width / QWEN3_PATCH_SIZE; |
| 182 | |
| 183 | // Pre-allocate final buffer to avoid reallocation |
| 184 | const uint32_t prev_pixel_values_size = pixel_values.size(); |
| 185 | pixel_values.resize(prev_pixel_values_size + total_patch_size); |
| 186 | |
| 187 | // Use non-optimized path for consistent results across platforms |
| 188 | auto resize_image = imgproc::avx512::resize_bicubic_antialias_rgb_planar_avx512( |
| 189 | image._data.data(), width, height, resized_width, resized_height, true |
| 190 | ); |
| 191 | |
| 192 | // Reuse scratch buffer across calls to avoid repeated allocations |
| 193 | static thread_local std::vector<float> patch_vector_scratch; |
| 194 | if (patch_vector_scratch.size() < total_patch_size) { |
| 195 | patch_vector_scratch.resize(total_patch_size); |
| 196 | } |
| 197 | |
| 198 | // Apply rescale and normalization to first frame |
| 199 | imgproc::avx512::rescale_and_normalize_avx512( |
| 200 | resize_image.data(), patch_vector_scratch.data(), |
| 201 | resized_width, resized_height, channels, |
| 202 | true, QWEN3_VISION_RESCALE_FACTOR, |
| 203 | true, QWEN3_VISION_RESCALE_IMAGE_MEAN, QWEN3_VISION_RESCALE_IMAGE_STD |
| 204 | ); |
| 205 | |
| 206 | // Replicate first frame for temporal patches (optimized for QWEN3_TEMPORAL_PATCH_SIZE = 2) |
| 207 | // This is more efficient than a loop for the common case |
| 208 | if constexpr (QWEN3_TEMPORAL_PATCH_SIZE == 2) { |
| 209 | memcpy( |
| 210 | patch_vector_scratch.data() + single_frame_size, |
| 211 | patch_vector_scratch.data(), |
| 212 | single_frame_size * sizeof(float) |
| 213 | ); |
| 214 | } else { |
| 215 | // Generic loop for other TEMPORAL_PATCH_SIZE values |
| 216 | for(unsigned l = 1; l < QWEN3_TEMPORAL_PATCH_SIZE; l++){ |
| 217 | memcpy( |
| 218 | patch_vector_scratch.data() + l * single_frame_size, |
nothing calls this directly
no test coverage detected