Download edge-impulse-sdk/dsp/image/processing.cpp from edgeimpulse/Hey-Edge: direct link, hf CLI and curl.
- Browser
- Download file 18.8 kB
-
https://huggingface.co/edgeimpulse/Hey-Edge/resolve/main/edge-impulse-sdk/dsp/image/processing.cpp
- Command line
-
hf download hf://edgeimpulse/Hey-Edge/edge-impulse-sdk/dsp/image/processing.cpp
-
curl -L -o processing.cpp https://huggingface.co/edgeimpulse/Hey-Edge/resolve/main/edge-impulse-sdk/dsp/image/processing.cpp
18.8 kB
| /* | |
| * Copyright (c) 2024 EdgeImpulse Inc. | |
| * | |
| * Generated by Edge Impulse and licensed under the applicable Edge Impulse | |
| * Terms of Service. Community and Professional Terms of Service | |
| * (https://edgeimpulse.com/legal/terms-of-service) or Enterprise Terms of | |
| * Service (https://edgeimpulse.com/legal/enterprise-terms-of-service), | |
| * according to your product plan subscription (the “License”). | |
| * | |
| * This software, documentation and other associated files (collectively referred | |
| * to as the “Software”) is a single SDK variation generated by the Edge Impulse | |
| * platform and requires an active paid Edge Impulse subscription to use this | |
| * Software for any purpose. | |
| * | |
| * You may NOT use this Software unless you have an active Edge Impulse subscription | |
| * that meets the eligibility requirements for the applicable License, subject to | |
| * your full and continued compliance with the terms and conditions of the License, | |
| * including without limitation any usage restrictions under the applicable License. | |
| * | |
| * If you do not have an active Edge Impulse product plan subscription, or if use | |
| * of this Software exceeds the usage limitations of your Edge Impulse product plan | |
| * subscription, you are not permitted to use this Software and must immediately | |
| * delete and erase all copies of this Software within your control or possession. | |
| * Edge Impulse reserves all rights and remedies available to enforce its rights. | |
| * | |
| * Unless required by applicable law or agreed to in writing, the Software is | |
| * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, | |
| * either express or implied. See the License for the specific language governing | |
| * permissions, disclaimers and limitations under the License. | |
| */ | |
| namespace ei { | |
| namespace image { | |
| namespace processing { | |
| /** | |
| * @brief Convert YUV to RGB | |
| * | |
| * @param rgb_out Output buffer (can be the same as yuv_in if big enough) | |
| * @param yuv_in Input buffer | |
| * @param in_size_B Size of input image in B | |
| * @param opts Note, only BIG_ENDIAN_ORDER supported presently | |
| */ | |
| int yuv422_to_rgb888( | |
| unsigned char *rgb_out, | |
| unsigned const char *yuv_in, | |
| unsigned int in_size_B, | |
| YUV_OPTIONS opts) | |
| { | |
| // Clamp out of range values | |
| // Color space conversion for RGB | |
| unsigned int in_size_pixels = in_size_B / 4; | |
| yuv_in += in_size_B - 1; | |
| int rgb_end = TEST_BIT_MASK(opts, PAD_4B) ? 2 * in_size_B : (6 * in_size_B) / 4; | |
| rgb_out += rgb_end - 1; | |
| // Going backwards probably looks strange, but | |
| // This allows us to do the algorithm in place! | |
| // User needs to put the YUV image into a larger buffer than necessary | |
| // But going backwards means we don't overwrite the YUV bytes | |
| // until we don't need them anymore | |
| for (unsigned int i = 0; i < in_size_pixels; ++i) { | |
| int y2 = *yuv_in-- - 16; | |
| int v = *yuv_in-- - 128; | |
| int y0 = *yuv_in-- - 16; | |
| int u0 = *yuv_in-- - 128; | |
| if (TEST_BIT_MASK(opts, BIG_ENDIAN_ORDER)) { | |
| *rgb_out-- = EI_CLAMP(EI_GET_B_FROM_YUV(y2, u0, v)); | |
| *rgb_out-- = EI_CLAMP(EI_GET_G_FROM_YUV(y2, u0, v)); | |
| *rgb_out-- = EI_CLAMP(EI_GET_R_FROM_YUV(y2, u0, v)); | |
| if (TEST_BIT_MASK(opts, PAD_4B)) { | |
| *rgb_out-- = 0; | |
| } | |
| *rgb_out-- = EI_CLAMP(EI_GET_B_FROM_YUV(y0, u0, v)); | |
| *rgb_out-- = EI_CLAMP(EI_GET_G_FROM_YUV(y0, u0, v)); | |
| *rgb_out-- = EI_CLAMP(EI_GET_R_FROM_YUV(y0, u0, v)); | |
| if (TEST_BIT_MASK(opts, PAD_4B)) { | |
| *rgb_out-- = 0; | |
| } | |
| } | |
| else { | |
| // not yet supported | |
| return EIDSP_NOT_SUPPORTED; | |
| } | |
| } | |
| return EIDSP_OK; | |
| } | |
| /** | |
| * @brief Crops an image. Can be in-place. 4B alignment for best performance | |
| * (Alignment is tested, will fall back to B by B movement) | |
| * | |
| * @param srcWidth X dimension in pixels | |
| * @param srcHeight Y dimension in pixels | |
| * @param srcImage Input buffer | |
| * @param startX X coord of first pixel to keep | |
| * @param startY Y coord of the first pixel to keep | |
| * @param dstWidth Desired X dimension in pixels (should be smaller than srcWidth) | |
| * @param dstHeight Desired Y dimension in pixels (should be smaller than srcHeight) | |
| * @param dstImage Output buffer, can be the same as srcImage | |
| * @param iBpp 8 or 16 for bits per pixel | |
| */ | |
| int cropImage( | |
| const uint8_t *srcImage, | |
| int srcWidth, | |
| int srcHeight, | |
| int startX, | |
| int startY, | |
| uint8_t *dstImage, | |
| int dstWidth, | |
| int dstHeight, | |
| int iBpp) | |
| { | |
| uint32_t *s32, *d32; | |
| int x, y; | |
| if (startX < 0 || startX >= srcWidth || startY < 0 || startY >= srcHeight || | |
| (startX + dstWidth) > srcWidth || (startY + dstHeight) > srcHeight) { | |
| return EIDSP_PARAMETER_INVALID; // invalid parameters | |
| } | |
| if (iBpp != 8 && iBpp != 16) { | |
| return EIDSP_PARAMETER_INVALID; | |
| } | |
| if (iBpp == 8) { | |
| const uint8_t *s; | |
| uint8_t *d; | |
| for (y = 0; y < dstHeight; y++) { | |
| s = &srcImage[srcWidth * (y + startY) + startX]; | |
| d = &dstImage[(dstWidth * y)]; | |
| x = 0; | |
| if ((intptr_t)s & 3 || (intptr_t)d & 3) { // either src or dst pointer is not aligned | |
| for (; x < dstWidth; x++) { | |
| *d++ = *s++; // have to do it byte-by-byte | |
| } | |
| } | |
| else { | |
| // move 4 bytes at a time if aligned or alignment not enforced | |
| s32 = (uint32_t *)s; | |
| d32 = (uint32_t *)d; | |
| for (; x < dstWidth - 3; x += 4) { | |
| *d32++ = *s32++; | |
| } | |
| // any remaining stragglers? | |
| s = (uint8_t *)s32; | |
| d = (uint8_t *)d32; | |
| for (; x < dstWidth; x++) { | |
| *d++ = *s++; | |
| } | |
| } | |
| } // for y | |
| } // 8-bpp | |
| else { | |
| uint16_t *s, *d; | |
| for (y = 0; y < dstHeight; y++) { | |
| s = (uint16_t *)&srcImage[2 * srcWidth * (y + startY) + startX * 2]; | |
| d = (uint16_t *)&dstImage[(dstWidth * y * 2)]; | |
| x = 0; | |
| if ((intptr_t)s & 2 || (intptr_t)d & 2) { // either src or dst pointer is not aligned | |
| for (; x < dstWidth; x++) { | |
| *d++ = *s++; // have to do it 16-bits at a time | |
| } | |
| } | |
| else { | |
| // move 4 bytes at a time if aligned or alignment no enforced | |
| s32 = (uint32_t *)s; | |
| d32 = (uint32_t *)d; | |
| for (; x < dstWidth - 1; x += 2) { // we can move 2 pixels at a time | |
| *d32++ = *s32++; | |
| } | |
| // any remaining stragglers? | |
| s = (uint16_t *)s32; | |
| d = (uint16_t *)d32; | |
| for (; x < dstWidth; x++) { | |
| *d++ = *s++; | |
| } | |
| } | |
| } // for y | |
| } // 16-bpp case | |
| return EIDSP_OK; | |
| } /* cropImage() */ | |
| /** | |
| * @copydoc cropImage( | |
| int srcWidth, | |
| int srcHeight, | |
| const uint8_t *srcImage, | |
| int startX, | |
| int startY, | |
| int dstWidth, | |
| int dstHeight, | |
| uint8_t *dstImage, | |
| int iBpp) | |
| */ | |
| int crop_image_rgb888_packed( | |
| const uint8_t *srcImage, | |
| int srcWidth, | |
| int srcHeight, | |
| int startX, | |
| int startY, | |
| uint8_t *dstImage, | |
| int dstWidth, | |
| int dstHeight) | |
| { | |
| // use 8 bpp mode, but do everything *3 for RGB | |
| return cropImage( | |
| srcImage, | |
| srcWidth * 3, | |
| srcHeight, | |
| startX * 3, | |
| startY, | |
| dstImage, | |
| dstWidth * 3, | |
| dstHeight, | |
| 8); | |
| } | |
| /** | |
| * @brief Resize an image using interpolation | |
| * Can be used to resize the image smaller or larger | |
| * If resizing much smaller than 1/3 size, then a more rubust algorithm should average all of the pixels | |
| * This algorithm uses bilinear interpolation - averages a 2x2 region to generate each new pixel | |
| * | |
| * @param srcWidth Input image width in pixels | |
| * @param srcHeight Input image height in pixels | |
| * @param srcImage Input buffer | |
| * @param dstWidth Output image width in pixels | |
| * @param dstHeight Output image height in pixels | |
| * @param dstImage Output buffer, can be same as input buffer | |
| * @param pixel_size_B Size of pixels in Bytes. 3 for RGB, 1 for mono | |
| */ | |
| int resize_image( | |
| const uint8_t *srcImage, | |
| int srcWidth, | |
| int srcHeight, | |
| uint8_t *dstImage, | |
| int dstWidth, | |
| int dstHeight, | |
| int pixel_size_B) | |
| { | |
| // Copied from ei_camera.cpp in firmware-eta-compute | |
| // Modified for RGB888 | |
| // This needs to be < 16 or it won't fit. Cortex-M4 only has SIMD for signed multiplies | |
| constexpr int FRAC_BITS = 14; | |
| constexpr int FRAC_VAL = (1 << FRAC_BITS); | |
| constexpr int FRAC_MASK = (FRAC_VAL - 1); | |
| uint32_t src_x_accum, src_y_accum; // accumulators and fractions for scaling the image | |
| uint32_t x_frac, nx_frac, y_frac, ny_frac; | |
| int x, y, ty; | |
| if (srcHeight < 2) { | |
| return EIDSP_PARAMETER_INVALID; | |
| } | |
| src_y_accum = 0; | |
| const uint32_t src_x_frac = (srcWidth * FRAC_VAL) / dstWidth; | |
| const uint32_t src_y_frac = (srcHeight * FRAC_VAL) / dstHeight; | |
| //from here out, *3 b/c RGB | |
| srcWidth *= pixel_size_B; | |
| //srcHeight not used for indexing | |
| //dstWidth still needed as is | |
| //dstHeight shouldn't be scaled | |
| const uint8_t *s; | |
| uint8_t *d; | |
| for (y = 0; y < dstHeight; y++) { | |
| // do indexing computations | |
| ty = src_y_accum >> FRAC_BITS; // src y | |
| y_frac = src_y_accum & FRAC_MASK; | |
| src_y_accum += src_y_frac; | |
| ny_frac = FRAC_VAL - y_frac; // y fraction and 1.0 - y fraction | |
| s = &srcImage[ty * srcWidth]; | |
| d = &dstImage[y * dstWidth * pixel_size_B]; //not scaled above | |
| src_x_accum = 0; | |
| for (x = 0; x < dstWidth; x++) { | |
| uint32_t tx, p00, p01, p10, p11; | |
| // do indexing computations | |
| tx = (src_x_accum >> FRAC_BITS) * pixel_size_B; | |
| x_frac = src_x_accum & FRAC_MASK; | |
| nx_frac = FRAC_VAL - x_frac; // x fraction and 1.0 - x fraction | |
| src_x_accum += src_x_frac; | |
| //interpolate and write out | |
| for (int color = 0; color < pixel_size_B; | |
| color++) // do pixel_size_B times for pixel_size_B colors | |
| { | |
| p00 = s[tx]; | |
| p10 = s[tx + pixel_size_B]; | |
| p01 = s[tx + srcWidth]; | |
| p11 = s[tx + srcWidth + pixel_size_B]; | |
| p00 = ((p00 * nx_frac) + (p10 * x_frac) + FRAC_VAL / 2) >> FRAC_BITS; // top line | |
| p01 = ((p01 * nx_frac) + (p11 * x_frac) + FRAC_VAL / 2) >> FRAC_BITS; // bottom line | |
| p00 = ((p00 * ny_frac) + (p01 * y_frac) + FRAC_VAL / 2) >> FRAC_BITS; //top + bottom | |
| *d++ = (uint8_t)p00; // store new pixel | |
| //ready next loop | |
| tx++; | |
| } | |
| } // for x | |
| } // for y | |
| return EIDSP_OK; | |
| } // resizeImage() | |
| /** | |
| * @brief Calculate new dims that match the aspect ratio of destination | |
| * This prevents a squashed look | |
| * The smallest axis is held constant | |
| * | |
| * @param srcWidth Input width in pixels | |
| * @param srcHeight Input height in pixels | |
| * @param dstWidth Ultimate width in pixels | |
| * @param dstHeight Ultimate height in pixels | |
| * @param[out] cropWidth Width in pixels that matches the aspect ratio | |
| * @param[out] cropHeight Height in pixels that matches the aspect ratio | |
| */ | |
| void calculate_crop_dims( | |
| int srcWidth, | |
| int srcHeight, | |
| int dstWidth, | |
| int dstHeight, | |
| int &cropWidth, | |
| int &cropHeight) | |
| { | |
| //first, trim the largest axis to match destination aspect ratio | |
| //calculate by fixing the smaller axis | |
| if (srcWidth > srcHeight) { | |
| cropWidth = (uint32_t)(dstWidth * srcHeight) / dstHeight; //cast in case int is small | |
| cropHeight = srcHeight; | |
| } | |
| else { | |
| cropHeight = (uint32_t)(dstHeight * srcWidth) / dstWidth; | |
| cropWidth = srcWidth; | |
| } | |
| } | |
| int crop_and_interpolate_rgb888( | |
| const uint8_t *srcImage, | |
| int srcWidth, | |
| int srcHeight, | |
| uint8_t *dstImage, | |
| int dstWidth, | |
| int dstHeight) | |
| { | |
| int cropWidth, cropHeight; | |
| // What are dimensions that maintain aspect ratio? | |
| calculate_crop_dims(srcWidth, srcHeight, dstWidth, dstHeight, cropWidth, cropHeight); | |
| // Now crop to that dimension | |
| int res = crop_image_rgb888_packed( | |
| srcImage, | |
| srcWidth, | |
| srcHeight, | |
| (srcWidth - cropWidth) / 2, | |
| (srcHeight - cropHeight) / 2, | |
| dstImage, | |
| cropWidth, | |
| cropHeight); | |
| if (res != EIDSP_OK) { | |
| return res; | |
| } | |
| // Finally, interpolate down to desired dimensions, in place | |
| return resize_image(dstImage, cropWidth, cropHeight, dstImage, dstWidth, dstHeight, 3); | |
| } | |
| int crop_and_interpolate_image( | |
| const uint8_t *srcImage, | |
| int srcWidth, | |
| int srcHeight, | |
| uint8_t *dstImage, | |
| int dstWidth, | |
| int dstHeight, | |
| int pixel_size_B) | |
| { | |
| int cropWidth, cropHeight; | |
| // What are dimensions that maintain aspect ratio? | |
| calculate_crop_dims(srcWidth, srcHeight, dstWidth, dstHeight, cropWidth, cropHeight); | |
| // Now crop to that dimension | |
| int res = cropImage( | |
| srcImage, | |
| srcWidth * pixel_size_B, | |
| srcHeight, | |
| ((srcWidth - cropWidth) / 2) * pixel_size_B, | |
| (srcHeight - cropHeight) / 2, | |
| dstImage, | |
| cropWidth * pixel_size_B, | |
| cropHeight, | |
| 8); | |
| if (res != EIDSP_OK) { | |
| return res; | |
| } | |
| // Finally, interpolate down to desired dimensions, in place | |
| return resize_image(dstImage, cropWidth, cropHeight, dstImage, dstWidth, dstHeight, pixel_size_B); | |
| } | |
| int resize_image_using_mode( | |
| const uint8_t *srcImage, | |
| int srcWidth, | |
| int srcHeight, | |
| uint8_t *dstImage, | |
| int dstWidth, | |
| int dstHeight, | |
| int pixel_size_B, | |
| int mode) | |
| { | |
| if (srcWidth == dstWidth && srcHeight == dstHeight) { | |
| EI_LOGI("Source and destination sizes are the same. No resizing needed.\n"); | |
| if (srcImage != dstImage) { | |
| memcpy(dstImage, srcImage, srcWidth * srcHeight * pixel_size_B); | |
| } | |
| return -1; // Return an error code | |
| } | |
| if (srcImage == dstImage && mode == EI_CLASSIFIER_RESIZE_FIT_LONGEST) { | |
| EI_LOGI("FIT LONGEST in place, make sure source is oversized to fit destination size\n"); | |
| } | |
| if (mode == EI_CLASSIFIER_RESIZE_FIT_SHORTEST) { | |
| int res = crop_and_interpolate_image( | |
| srcImage, | |
| srcWidth, | |
| srcHeight, | |
| dstImage, | |
| dstWidth, | |
| dstHeight, | |
| pixel_size_B); | |
| if (res != 0) { | |
| EI_LOGE("Error in crop_and_interpolate_image: %d\n", res); | |
| return res; | |
| } | |
| return 0; | |
| } | |
| if (mode == EI_CLASSIFIER_RESIZE_SQUASH) { | |
| int res = | |
| resize_image(srcImage, srcWidth, srcHeight, dstImage, dstWidth, dstHeight, pixel_size_B); | |
| if (res != 0) { | |
| EI_LOGE("Error in resize_image: %d\n", res); | |
| return res; | |
| } | |
| return 0; | |
| } | |
| if (mode == EI_CLASSIFIER_RESIZE_FIT_LONGEST) { | |
| // Calculate aspect ratios | |
| float srcAspect = static_cast<float>(srcWidth) / srcHeight; | |
| float dstAspect = static_cast<float>(dstWidth) / dstHeight; | |
| // Calculate new dimensions | |
| int resizeWidth, resizeHeight; | |
| if (srcAspect > dstAspect) { | |
| resizeWidth = dstWidth; | |
| resizeHeight = static_cast<int>(dstWidth / srcAspect); | |
| } | |
| else { | |
| resizeHeight = dstHeight; | |
| resizeWidth = static_cast<int>(dstHeight * srcAspect); | |
| } | |
| // Calculate the start position for the resized image in the destination buffer | |
| int startX = (dstWidth - resizeWidth) / 2; | |
| int startY = (dstHeight - resizeHeight) / 2; | |
| // First, resize in place. We can't resize into the middle as this may destroy source pixels needed later | |
| int res = resize_image( | |
| srcImage, | |
| srcWidth, | |
| srcHeight, | |
| dstImage, | |
| resizeWidth, | |
| resizeHeight, | |
| pixel_size_B); | |
| if (res != 0) { | |
| EI_LOGE("Error in resize_image: %d\n", res); | |
| return res; | |
| } | |
| // Zero out top and bottom easily | |
| if (resizeWidth == dstWidth) { | |
| // Now move the image to the correct location | |
| size_t dstStart = (startY * dstWidth + startX) * pixel_size_B; | |
| memmove( | |
| dstImage + dstStart, | |
| dstImage, | |
| resizeWidth * resizeHeight * pixel_size_B); | |
| // Zero out the right points before and after the new image | |
| memset(dstImage, 0, dstStart); | |
| // Zero out the bottom part | |
| // Can just use the start addr as size again b/c it's symmetrical | |
| memset( | |
| dstImage + dstStart + resizeWidth * resizeHeight * pixel_size_B, | |
| 0, | |
| dstStart); | |
| } | |
| // Sides are more work | |
| else { | |
| size_t resizeIndex = (dstWidth * dstHeight - resizeWidth * resizeHeight) * pixel_size_B; | |
| // Move the image to the end of the dstBuffer | |
| memmove(dstImage + resizeIndex, dstImage, resizeWidth * resizeHeight * pixel_size_B); | |
| // Now move one row at a time, zero padding sides along the way | |
| for (int y = 0; y < resizeHeight; y++) { | |
| memmove( | |
| dstImage + ((startY + y) * dstWidth + startX) * pixel_size_B, | |
| dstImage + resizeIndex, | |
| resizeWidth * pixel_size_B); | |
| resizeIndex += resizeWidth * pixel_size_B; | |
| // Zero out the left and right sides | |
| memset(dstImage + (startY + y) * dstWidth * pixel_size_B, 0, startX * pixel_size_B); | |
| memset(dstImage + ((startY + y) * dstWidth + startX + resizeWidth) * pixel_size_B, 0, (dstWidth - startX - resizeWidth) * pixel_size_B); | |
| } | |
| } | |
| return 0; | |
| } // fit longest | |
| // shouldn't get here | |
| return -2; | |
| } | |
| } //namespaces | |
| } | |
| } | |