File size: 6,106 Bytes
9422383 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 | #include <arm_neon.h>
#include <algorithm>
#include <array>
#include <cmath>
#include <limits>
#include "arm_yolo26n.h"
#include "buffer_validation.h"
#include "model_contract.h"
namespace {
// Preprocessing preserves image pixels and pads the shorter dimension with the
// same value used by YOLO letterboxing. Source bytes are normalized from
// [0, 255] to [0, 1] while packed RGB/BGR channels are separated into the
// planar red, green, and blue tensors consumed by the runtime.
constexpr uint8_t kLetterboxValue = 114;
constexpr float kByteScale = 1.0F / 255.0F;
// Scalar pixels and padding use a lookup table so every byte value receives
// the same floating-point conversion without repeating division in the loops.
const std::array<float, 256>& NormalizedValues() {
static const std::array<float, 256> values = []() {
std::array<float, 256> result{};
for (size_t index = 0; index < result.size(); ++index) {
result[index] = static_cast<float>(index) / 255.0F;
}
return result;
}();
return values;
}
// Normalize eight values at once for the vectorized body of each image row.
void StoreNormalized8(uint8x8_t bytes, float* output) {
const uint16x8_t values = vmovl_u8(bytes);
const float32x4_t scale = vdupq_n_f32(kByteScale);
vst1q_f32(output, vmulq_f32(vcvtq_f32_u32(vmovl_u16(vget_low_u16(values))), scale));
vst1q_f32(output + 4, vmulq_f32(vcvtq_f32_u32(vmovl_high_u16(values)), scale));
}
} // namespace
extern "C" y26_native_status y26_native_preprocess_image_no_resize(
const uint8_t* image, size_t image_bytes, size_t height, size_t width, size_t row_stride_bytes,
y26_native_pixel_format pixel_format, float* output, size_t output_bytes) {
// Reject missing buffers, empty images, and output sizes that cannot contain
// three equal float planes.
if (!image || !output || !height || !width || output_bytes % (3 * sizeof(float))) {
return Y26_NATIVE_INVALID_ARGUMENT;
}
// Derive the square model resolution from the caller's output allocation.
// This no-resize entry point accepts only supported resolutions and images
// whose longer side already equals that resolution.
const size_t input_plane = output_bytes / (3 * sizeof(float));
const size_t input_size = static_cast<size_t>(std::sqrt(static_cast<double>(input_plane)));
if (input_size * input_size != input_plane || input_size > std::numeric_limits<uint32_t>::max() ||
!yolo26::IsSupportedResolution(static_cast<uint32_t>(input_size)) || height > input_size ||
width > input_size || std::max(height, width) != input_size ||
pixel_format < Y26_NATIVE_BGR || pixel_format > Y26_NATIVE_BGRX ||
output_bytes != 3 * input_plane * sizeof(float)) {
return Y26_NATIVE_INVALID_ARGUMENT;
}
// Validate the packed source layout before calculating its final byte. A row
// may include trailing stride padding, but it must contain every image pixel.
const size_t pixel_stride = pixel_format == Y26_NATIVE_BGRX ? 4 : 3;
if (width > std::numeric_limits<size_t>::max() / pixel_stride ||
row_stride_bytes < width * pixel_stride) {
return Y26_NATIVE_INVALID_ARGUMENT;
}
const size_t final_row_bytes = width * pixel_stride;
if ((height - 1) > (std::numeric_limits<size_t>::max() - final_row_bytes) / row_stride_bytes) {
return Y26_NATIVE_INVALID_ARGUMENT;
}
const size_t required_image_bytes = (height - 1) * row_stride_bytes + final_row_bytes;
if (image_bytes < required_image_bytes ||
yolo26::BuffersOverlap(image, required_image_bytes, output, output_bytes)) {
return Y26_NATIVE_INVALID_ARGUMENT;
}
const auto& normalized = NormalizedValues();
// Centered padding implements the no-resize half of letterboxing. The ABI
// requires one image side to match the model, so only one axis is padded.
const size_t top = (input_size - height) / 2;
const size_t left = (input_size - width) / 2;
float* red_plane = output;
float* green_plane = output + input_plane;
float* blue_plane = output + 2 * input_plane;
for (float* plane : {red_plane, green_plane, blue_plane}) {
std::fill(plane, plane + top * input_size, normalized[kLetterboxValue]);
std::fill(plane + (top + height) * input_size, plane + input_plane,
normalized[kLetterboxValue]);
if (left) {
for (size_t y = top; y < top + height; ++y) {
float* row = plane + y * input_size;
std::fill(row, row + left, normalized[kLetterboxValue]);
std::fill(row + left + width, row + input_size, normalized[kLetterboxValue]);
}
}
}
const size_t red_index = pixel_format == Y26_NATIVE_RGB ? 0 : 2;
const size_t blue_index = 2 - red_index;
// Separate packed source channels into planar output. Complete groups of
// eight pixels use NEON; the scalar loop handles the remaining row tail.
for (size_t y = 0; y < height; ++y) {
const uint8_t* source = image + y * row_stride_bytes;
const size_t offset = (top + y) * input_size + left;
float* red = red_plane + offset;
float* green = green_plane + offset;
float* blue = blue_plane + offset;
size_t x = 0;
for (; x + 8 <= width; x += 8) {
if (pixel_format == Y26_NATIVE_BGRX) {
const uint8x8x4_t pixels = vld4_u8(source + 4 * x);
StoreNormalized8(pixels.val[2], red + x);
StoreNormalized8(pixels.val[1], green + x);
StoreNormalized8(pixels.val[0], blue + x);
} else {
const uint8x8x3_t pixels = vld3_u8(source + 3 * x);
StoreNormalized8(pixels.val[red_index], red + x);
StoreNormalized8(pixels.val[1], green + x);
StoreNormalized8(pixels.val[blue_index], blue + x);
}
}
for (; x < width; ++x) {
const uint8_t* pixel = source + pixel_stride * x;
if (pixel_format == Y26_NATIVE_RGB) {
red[x] = normalized[pixel[0]];
green[x] = normalized[pixel[1]];
blue[x] = normalized[pixel[2]];
} else {
blue[x] = normalized[pixel[0]];
green[x] = normalized[pixel[1]];
red[x] = normalized[pixel[2]];
}
}
}
return Y26_NATIVE_OK;
}
|