diff --git a/mindspore/ccsrc/minddata/dataset/kernels/image/lite_cv/CMakeLists.txt b/mindspore/ccsrc/minddata/dataset/kernels/image/lite_cv/CMakeLists.txt index 71adc49cd5..5c85ef418a 100644 --- a/mindspore/ccsrc/minddata/dataset/kernels/image/lite_cv/CMakeLists.txt +++ b/mindspore/ccsrc/minddata/dataset/kernels/image/lite_cv/CMakeLists.txt @@ -3,4 +3,6 @@ set_property(SOURCE ${_CURRENT_SRC_FILES} PROPERTY COMPILE_DEFINITIONS SUBMODULE add_library(lite-cv OBJECT image_process.cc warp_affine.cc + gaussian_blur.cc + canny.cc lite_mat.cc) \ No newline at end of file diff --git a/mindspore/ccsrc/minddata/dataset/kernels/image/lite_cv/canny.cc b/mindspore/ccsrc/minddata/dataset/kernels/image/lite_cv/canny.cc new file mode 100644 index 0000000000..1c165fc28e --- /dev/null +++ b/mindspore/ccsrc/minddata/dataset/kernels/image/lite_cv/canny.cc @@ -0,0 +1,257 @@ +/** + * Copyright 2021 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License 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 and + * limitations under the License. + */ + +#include + +#include "lite_cv/lite_mat.h" +#include "lite_cv/image_process.h" + +#ifdef ENABLE_ANDROID +#if defined(__arm__) || defined(__aarch64__) || defined(_M_ARM) || defined(_M_ARM64) +#define USE_NEON +#include +#endif +#endif + +#define ANGLE_22_5 0.39269908169872414 +#define ANGLE_67_5 1.1780972450961724 + +namespace mindspore { +namespace dataset { + +static void GetSobelKernel(float *kernel, int flag, int ksize) { + std::vector buffer(ksize + 1); + float *ptr = kernel; + + if (ksize == 1) { + buffer[0] = 1; + } else if (ksize == 3) { + if (flag == 0) { + buffer[0] = 1, buffer[1] = 2, buffer[2] = 1; + } else if (flag == 1) { + buffer[0] = -1, buffer[1] = 0, buffer[2] = 1; + } else { + buffer[0] = 1, buffer[1] = -2, buffer[2] = 1; + } + } else { + int old, now; + buffer[0] = 1; + for (int i = 0; i < ksize; i++) { + buffer[i + 1] = 0; + } + for (int i = 0; i < ksize - flag - 1; i++) { + old = buffer[0]; + for (int j = 1; j <= ksize; j++) { + now = buffer[j] + buffer[j - 1]; + buffer[j - 1] = old; + old = now; + } + } + for (int i = 0; i < flag; i++) { + old = -buffer[0]; + for (int j = 1; j <= ksize; j++) { + now = buffer[j - 1] - buffer[j]; + buffer[j - 1] = old; + old = now; + } + } + } + + for (int i = 0; i < ksize; i++) { + ptr[i] = buffer[i]; + } +} + +bool Sobel(const LiteMat &src, LiteMat &dst, int flag_x, int flag_y, int ksize, PaddBorderType pad_type) { // NOLINT + if (src.IsEmpty() || src.data_type_ != LDataType::UINT8) { + return false; + } + if (flag_x < 0 || flag_y < 0 || flag_x + flag_y <= 0 || flag_x >= ksize || flag_y >= ksize) { + return false; + } + + if (dst.IsEmpty() || dst.width_ != src.width_ || dst.height_ != src.height_ || dst.channel_ != src.channel_ || + dst.data_type_ != LDataType::FLOAT32) { + dst.Init(src.width_, src.height_, src.channel_, LDataType::FLOAT32); + } + + LiteMat kx, ky; + kx.Init(ksize, 1, 1, LDataType::FLOAT32); + ky.Init(1, ksize, 1, LDataType::FLOAT32); + + GetSobelKernel(kx, flag_x, ksize); + GetSobelKernel(ky, flag_y, ksize); + + return ConvRowCol(src, kx, ky, dst, LDataType::FLOAT32, pad_type); +} + +static float GetEdge(const float *temp, int width, int height, int x, int y) { + if (x >= 0 && y >= 0 && x < width && y < height) { + return temp[y * width + x]; + } else { + return -1.0f; + } +} + +static void NonMaximumSuppression(const LiteMat &gx, const LiteMat &gy, LiteMat &edges, bool L2gradient) { // NOLINT + edges.Init(gx.width_, gx.height_, gx.channel_, gx.data_type_); + + const float *gx_ptr = gx; + const float *gy_ptr = gy; + float *edges_ptr = edges; + + int size = gx.height_ * gx.width_; + float *temp = new float[size]; + for (int i = 0; i < size; i++) { + float gx_value = gx_ptr[i]; + float gy_value = gy_ptr[i]; + if (L2gradient) { + temp[i] = sqrt(gx_value * gx_value + gy_value * gy_value); + } else { + temp[i] = abs(gx_value) + abs(gy_value); + } + } + + for (int y = 0; y < gx.height_; y++) { + for (int x = 0; x < gx.width_; x++) { + float gx_value = gx_ptr[y * gx.width_ + x]; + float gy_value = gy_ptr[y * gx.width_ + x]; + + float gx_value_abs = std::abs(gx_value); + float gy_value_abs = std::abs(gy_value); + float angle_value = atan2(gy_value_abs, gx_value_abs); + float edge_value = temp[y * gx.width_ + x]; + float edge_pre, edge_nex; + if (angle_value < ANGLE_22_5) { + edge_pre = GetEdge(temp, gx.width_, gx.height_, x - 1, y); + edge_nex = GetEdge(temp, gx.width_, gx.height_, x + 1, y); + if (edge_value > edge_pre && edge_value >= edge_nex) { + edges_ptr[y * gx.width_ + x] = temp[y * gx.width_ + x]; + } else { + edges_ptr[y * gx.width_ + x] = 0.f; + } + } else if (angle_value > ANGLE_67_5) { + edge_pre = GetEdge(temp, gx.width_, gx.height_, x, y - 1); + edge_nex = GetEdge(temp, gx.width_, gx.height_, x, y + 1); + if (edge_value > edge_pre && edge_value >= edge_nex) { + edges_ptr[y * gx.width_ + x] = temp[y * gx.width_ + x]; + } else { + edges_ptr[y * gx.width_ + x] = 0.f; + } + } else if (gx_value * gy_value < 0) { + edge_pre = GetEdge(temp, gx.width_, gx.height_, x + 1, y - 1); + edge_nex = GetEdge(temp, gx.width_, gx.height_, x - 1, y + 1); + if (edge_value > edge_pre && edge_value > edge_nex) { + edges_ptr[y * gx.width_ + x] = temp[y * gx.width_ + x]; + } else { + edges_ptr[y * gx.width_ + x] = 0.f; + } + } else { + edge_pre = GetEdge(temp, gx.width_, gx.height_, x - 1, y - 1); + edge_nex = GetEdge(temp, gx.width_, gx.height_, x + 1, y + 1); + if (edge_value > edge_pre && edge_value > edge_nex) { + edges_ptr[y * gx.width_ + x] = temp[y * gx.width_ + x]; + } else { + edges_ptr[y * gx.width_ + x] = 0.f; + } + } + } + } +} + +static void Hysteresis(const LiteMat &edges, uint8_t *dst, double low_thresh, double high_thresh) { + const float *edges_ptr = edges; + uint8_t *dst_ptr = dst; + + int size = edges.height_ * edges.width_; + int *buffer = new int[size]; + int buffer_step = edges.width_; + std::vector stack; + for (int y = 0; y < edges.height_; y++) { + for (int x = 0; x < edges.width_; x++) { + int pos = y * edges.width_ + x; + float edge_value = edges_ptr[pos]; + if (edge_value > high_thresh) { + buffer[pos] = 2; + stack.push_back(pos); + } else if (edge_value <= low_thresh) { + buffer[pos] = 0; + } else { + buffer[pos] = 1; + } + } + } + + while (!stack.empty()) { + int pos = stack.back(); + stack.pop_back(); + int y = static_cast(pos / buffer_step); + int x = pos % buffer_step; + for (int i = -1; i < 2; i++) { + for (int j = -1; j < 2; j++) { + int next_y = y + i; + int next_x = x + j; + if (next_y < 0 || next_x < 0 || next_y >= edges.height_ || next_x >= edges.width_ || + (next_y == y && next_x == x)) { + continue; + } + int next = next_y * buffer_step + next_x; + if (buffer[next] == 1) { + buffer[next] = 2; + stack.push_back(next); + } + } + } + } + + for (int i = 0; i < size; i++) { + if (buffer[i] == 2) { + dst_ptr[i] = 255; + } else { + dst_ptr[i] = 0; + } + } +} + +bool Canny(const LiteMat &src, LiteMat &dst, double low_thresh, double high_thresh, int ksize, // NOLINT + bool L2gradient) { + if (src.IsEmpty() || src.data_type_ != LDataType::UINT8 || src.channel_ != 1) { + return false; + } + if (low_thresh < 0 || high_thresh < 0 || low_thresh > high_thresh) { + return false; + } + if ((ksize & 1) == 0 || ksize < 3 || ksize > 7) { + return false; + } + if (dst.IsEmpty() || dst.width_ != src.width_ || dst.height_ != src.height_ || dst.channel_ != src.channel_ || + dst.data_type_ != src.data_type_) { + dst.Init(src.width_, src.height_, src.channel_, src.data_type_); + } + + LiteMat gx, gy; + Sobel(src, gx, 1, 0, ksize, PaddBorderType::PADD_BORDER_REPLICATE); + Sobel(src, gy, 0, 1, ksize, PaddBorderType::PADD_BORDER_REPLICATE); + + LiteMat edges; + NonMaximumSuppression(gx, gy, edges, L2gradient); + + Hysteresis(edges, dst, low_thresh, high_thresh); + return true; +} + +} // namespace dataset +} // namespace mindspore diff --git a/mindspore/ccsrc/minddata/dataset/kernels/image/lite_cv/gaussian_blur.cc b/mindspore/ccsrc/minddata/dataset/kernels/image/lite_cv/gaussian_blur.cc new file mode 100644 index 0000000000..d8c1e12e2f --- /dev/null +++ b/mindspore/ccsrc/minddata/dataset/kernels/image/lite_cv/gaussian_blur.cc @@ -0,0 +1,90 @@ +/** + * Copyright 2021 Huawei Technologies Co., Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License 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 and + * limitations under the License. + */ + +#include + +#include "lite_cv/lite_mat.h" +#include "lite_cv/image_process.h" + +#ifdef ENABLE_ANDROID +#if defined(__arm__) || defined(__aarch64__) || defined(_M_ARM) || defined(_M_ARM64) +#define USE_NEON +#include +#endif +#endif + +namespace mindspore { +namespace dataset { + +static void GetGaussianKernel(float *kernel, int size, double sigma) { + int n = (size - 1) / 2; + std::vector buffer(n); + float sum = 0; + for (int i = 0; i < n; i++) { + int x = i - n; + float g = exp(-0.5 * x * x / (sigma * sigma)); + buffer[i] = g; + sum += g; + } + sum = sum * 2 + 1; + if ((size & 1) == 0) { + sum += 1; + } + + float scale = 1. / sum; + float *ptr = kernel; + for (int i = 0; i < n; i++) { + float g = buffer[i] * scale; + ptr[i] = g; + ptr[size - 1 - i] = g; + } + ptr[n] = scale; + if ((size & 1) == 0) { + ptr[n + 1] = scale; + } +} + +bool GaussianBlur(const LiteMat &src, LiteMat &dst, const std::vector &ksize, double sigmaX, // NOLINT + double sigmaY, PaddBorderType pad_type) { + if (src.IsEmpty() || src.data_type_ != LDataType::UINT8) { + return false; + } + if (ksize.size() != 2 || ksize[0] <= 0 || ksize[1] <= 0 || ksize[0] % 2 != 1 || ksize[1] % 2 != 1) { + return false; + } + if (sigmaX <= 0) { + return false; + } + if (sigmaY <= 0) { + sigmaY = sigmaX; + } + if (ksize[0] == 1 && ksize[1] == 1) { + dst = src; + return true; + } + + LiteMat kx, ky; + kx.Init(ksize[0], 1, 1, LDataType::FLOAT32); + ky.Init(1, ksize[1], 1, LDataType::FLOAT32); + + GetGaussianKernel(kx, ksize[0], sigmaX); + GetGaussianKernel(ky, ksize[1], sigmaY); + + return ConvRowCol(src, kx, ky, dst, src.data_type_, pad_type); +} + +} // namespace dataset +} // namespace mindspore diff --git a/mindspore/ccsrc/minddata/dataset/kernels/image/lite_cv/image_process.cc b/mindspore/ccsrc/minddata/dataset/kernels/image/lite_cv/image_process.cc index 6a90845b7b..64116e712c 100644 --- a/mindspore/ccsrc/minddata/dataset/kernels/image/lite_cv/image_process.cc +++ b/mindspore/ccsrc/minddata/dataset/kernels/image/lite_cv/image_process.cc @@ -1,5 +1,5 @@ /** - * Copyright 2020 Huawei Technologies Co., Ltd + * Copyright 2020-2021 Huawei Technologies Co., Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,10 +17,10 @@ #include "minddata/dataset/kernels/image/lite_cv/image_process.h" #include -#include #include #include #include +#include #include #include #include @@ -29,19 +29,12 @@ #include #endif -#ifdef ENABLE_NEON #define R2GRAY 9798 #define G2GRAY 19235 #define B2GRAY 3735 #define GRAYSHIFT 15 #define GRAYSHIFT_DELTA (1 << (GRAYSHIFT - 1)) #define U32TOU8CAST(value) ((uint8_t)std::min(value, (uint32_t)UCHAR_MAX)) -#else -#define R2GRAY 77 -#define G2GRAY 150 -#define B2GRAY 29 -#define GRAYSHIFT 8 -#endif #define YSCALE 0x0101 #define UTOB (-128) @@ -247,6 +240,125 @@ static void ResizeBilinear1C(const unsigned char *src, int src_width, int src_he delete[] data_buf; } +static inline uint8_t clip(float value, int min = 0, int max = 255) { + int int_val = roundf(value); + return std::max(std::numeric_limits::min(), + std::min(std::numeric_limits::max(), int_val)); +} + +template +static bool Conv2DImplement(const LiteMat &src, const LiteMat &kernel, T2 *dst, LDataType dst_type, + PaddBorderType pad_type) { + int border_x = static_cast(kernel.width_ / 2); + int border_y = static_cast(kernel.height_ / 2); + + LiteMat pad_mat; + pad_mat.Init(src.width_ + 2 * border_x, src.height_ + 2 * border_y, src.channel_, src.data_type_); + + if (!Pad(src, pad_mat, border_y, border_y, border_x, border_x, pad_type)) { + return false; + } + + const T1 *pad_ptr = pad_mat; + const float *kernel_ptr = kernel; + T2 *dst_ptr = dst; + + int pad_step = pad_mat.width_ * pad_mat.channel_; + int dst_step = src.width_ * src.channel_; + + if (src.channel_ == 1) { + for (int y = border_y; y < pad_mat.height_ - border_y; y++) { + for (int x = border_x; x < pad_mat.width_ - border_x; x++) { + float conv_sum = 0; + for (int i = -border_y; i < -border_y + kernel.height_; i++) { + for (int j = -border_x; j < -border_x + kernel.width_; j++) { + conv_sum += pad_ptr[(y + i) * pad_step + (x + j) * pad_mat.channel_] * + kernel_ptr[(i + border_y) * kernel.width_ + (j + border_x)]; + } + } + if (dst_type == LDataType::UINT8) { + dst_ptr[(y - border_y) * dst_step + (x - border_x) * src.channel_] = clip(conv_sum); + } else { + dst_ptr[(y - border_y) * dst_step + (x - border_x) * src.channel_] = conv_sum; + } + } + } + } else if (src.channel_ == 3) { + for (int y = border_y; y < pad_mat.height_ - border_y; y++) { + for (int x = border_x; x < pad_mat.width_ - border_x; x++) { + float conv_sum_b = 0; + float conv_sum_g = 0; + float conv_sum_r = 0; + for (int i = -border_y; i < -border_y + kernel.height_; i++) { + for (int j = -border_x; j < -border_x + kernel.width_; j++) { + conv_sum_b += pad_ptr[(y + i) * pad_step + (x + j) * pad_mat.channel_] * + kernel_ptr[(i + border_y) * kernel.width_ + (j + border_x)]; + conv_sum_g += pad_ptr[(y + i) * pad_step + (x + j) * pad_mat.channel_ + 1] * + kernel_ptr[(i + border_y) * kernel.width_ + (j + border_x)]; + conv_sum_r += pad_ptr[(y + i) * pad_step + (x + j) * pad_mat.channel_ + 2] * + kernel_ptr[(i + border_y) * kernel.width_ + (j + border_x)]; + } + } + if (dst_type == LDataType::UINT8) { + dst_ptr[(y - border_y) * dst_step + (x - border_x) * src.channel_] = clip(conv_sum_b); + dst_ptr[(y - border_y) * dst_step + (x - border_x) * src.channel_ + 1] = clip(conv_sum_g); + dst_ptr[(y - border_y) * dst_step + (x - border_x) * src.channel_ + 2] = clip(conv_sum_r); + } else { + dst_ptr[(y - border_y) * dst_step + (x - border_x) * src.channel_] = conv_sum_b; + dst_ptr[(y - border_y) * dst_step + (x - border_x) * src.channel_ + 1] = conv_sum_g; + dst_ptr[(y - border_y) * dst_step + (x - border_x) * src.channel_ + 2] = conv_sum_r; + } + } + } + } else { + return false; + } + return true; +} + +bool Conv2D(const LiteMat &src, const LiteMat &kernel, LiteMat &dst, LDataType dst_type, PaddBorderType pad_type) { + if (src.IsEmpty() || kernel.IsEmpty()) { + return false; + } + if ((dst_type != LDataType::UINT8 && dst_type != LDataType::FLOAT32) || kernel.data_type_ != LDataType::FLOAT32) { + return false; + } + if (dst.IsEmpty() || dst.width_ != src.width_ || dst.height_ != src.height_ || dst.channel_ != src.channel_ || + dst.data_type_ != dst_type) { + dst.Init(src.width_, src.height_, src.channel_, dst_type); + } + + if (src.data_type_ == LDataType::UINT8 && dst.data_type_ == LDataType::UINT8) { + return Conv2DImplement(src, kernel, dst, dst_type, pad_type); + } else if (src.data_type_ == LDataType::UINT8 && dst.data_type_ == LDataType::FLOAT32) { + return Conv2DImplement(src, kernel, dst, dst_type, pad_type); + } else if (src.data_type_ == LDataType::FLOAT32 && dst.data_type_ == LDataType::UINT8) { + return Conv2DImplement(src, kernel, dst, dst_type, pad_type); + } else if (src.data_type_ == LDataType::FLOAT32 && dst.data_type_ == LDataType::FLOAT32) { + return Conv2DImplement(src, kernel, dst, dst_type, pad_type); + } else { + return false; + } +} + +bool ConvRowCol(const LiteMat &src, const LiteMat &kx, const LiteMat &ky, LiteMat &dst, LDataType dst_type, + PaddBorderType pad_type) { + if (src.IsEmpty() || kx.IsEmpty() || ky.IsEmpty()) { + return false; + } + if (dst_type != LDataType::UINT8 && dst_type != LDataType::FLOAT32) { + return false; + } + if (dst.IsEmpty() || dst.width_ != src.width_ || dst.height_ != src.height_ || dst.channel_ != src.channel_ || + dst.data_type_ != dst_type) { + dst.Init(src.width_, src.height_, src.channel_, dst_type); + } + + LiteMat mid; + bool ret = Conv2D(src, kx, mid, LDataType::FLOAT32, pad_type) && Conv2D(mid, ky, dst, dst_type, pad_type); + return ret; +} + bool ResizeBilinear(const LiteMat &src, LiteMat &dst, int dst_w, int dst_h) { if (dst_h <= 0 || dst_w <= 0) { return false; @@ -485,7 +597,7 @@ static bool ConvertRGBAToGRAY(const unsigned char *data, LDataType data_type, in #else for (int y = 0; y < h; y++) { for (int x = 0; x < w; x++) { - *ptr = (data_ptr[2] * B2GRAY + data_ptr[1] * G2GRAY + data_ptr[0] * R2GRAY) >> GRAYSHIFT; + *ptr = (data_ptr[2] * B2GRAY + data_ptr[1] * G2GRAY + data_ptr[0] * R2GRAY + GRAYSHIFT_DELTA) >> GRAYSHIFT; ptr++; data_ptr += 4; } @@ -763,6 +875,45 @@ static void PadWithConstant(const LiteMat &src, LiteMat &dst, const int top, con } } +static int PadFromPos(int p, int len, PaddBorderType pad_type) { + if (p >= 0 && p < len) { + return p; + } + if (pad_type == PaddBorderType::PADD_BORDER_REPLICATE) { + return p < 0 ? 0 : len - 1; + } else { + return p < 0 ? -p : 2 * len - p - 2; + } +} + +template +static void PadImplement(const LiteMat &src, LiteMat &dst, const int top, const int bottom, const int left, + const int right, const PaddBorderType pad_type) { + int src_step = src.width_ * src.channel_; + int dst_step = dst.width_ * dst.channel_; + + uint8_t *src_data_ptr = reinterpret_cast(src.data_ptr_); + uint8_t *dst_data_ptr = reinterpret_cast(dst.data_ptr_); + for (int i = 0; i < src.height_; i++) { + memcpy(dst_data_ptr + (i + top) * dst.steps_[0] + left * dst.steps_[1], src_data_ptr + i * src.steps_[0], + src.steps_[0]); + } + + const T *src_ptr = src; + T *dst_ptr = dst; + for (int y = 0; y < dst.height_; y++) { + for (int x = 0; x < dst.width_; x++) { + if (y < top || y >= dst.height_ - bottom || x < left || x >= dst.width_ - right) { + int src_y = PadFromPos(y - top, src.height_, pad_type); + int src_x = PadFromPos(x - left, src.width_, pad_type); + for (int cn = 0; cn < dst.channel_; cn++) { + dst_ptr[y * dst_step + x * dst.channel_ + cn] = src_ptr[src_y * src_step + src_x * src.channel_ + cn]; + } + } + } + } +} + template void ExtractChannelImpl(const T *src_ptr, T *dst_ptr, int height, int width, int channel, int col) { int total = height * width; @@ -909,6 +1060,10 @@ bool Pad(const LiteMat &src, LiteMat &dst, int top, int bottom, int left, int ri PadWithConstant(src, dst, top, bottom, left, right, pad_type, fill_b_or_gray, fill_g, fill_r); } else if (pad_type == PADD_BORDER_CONSTANT && src.data_type_ == LDataType::UINT8) { PadWithConstant(src, dst, top, bottom, left, right, pad_type, fill_b_or_gray, fill_g, fill_r); + } else if (src.data_type_ == LDataType::FLOAT32) { + PadImplement(src, dst, top, bottom, left, right, pad_type); + } else if (src.data_type_ == LDataType::UINT8) { + PadImplement(src, dst, top, bottom, left, right, pad_type); } else { return false; } diff --git a/mindspore/ccsrc/minddata/dataset/kernels/image/lite_cv/image_process.h b/mindspore/ccsrc/minddata/dataset/kernels/image/lite_cv/image_process.h index 37a2873f39..5606bf5029 100644 --- a/mindspore/ccsrc/minddata/dataset/kernels/image/lite_cv/image_process.h +++ b/mindspore/ccsrc/minddata/dataset/kernels/image/lite_cv/image_process.h @@ -32,7 +32,12 @@ namespace dataset { #define INT16_CAST(X) \ static_cast(::std::min(::std::max(static_cast(X + (X >= 0.f ? 0.5f : -0.5f)), -32768), 32767)); -enum PaddBorderType { PADD_BORDER_CONSTANT = 0, PADD_BORDER_REPLICATE = 1 }; +enum PaddBorderType { + PADD_BORDER_CONSTANT = 0, + PADD_BORDER_REPLICATE = 1, + PADD_BORDER_REFLECT_101 = 4, + PADD_BORDER_DEFAULT = PADD_BORDER_REFLECT_101 +}; struct BoxesConfig { public: @@ -65,7 +70,7 @@ bool SubStractMeanNormalize(const LiteMat &src, LiteMat &dst, const std::vector< /// \brief padd image, the channel supports is 3 and 1 bool Pad(const LiteMat &src, LiteMat &dst, int top, int bottom, int left, int right, PaddBorderType pad_type, - uint8_t fill_b_or_gray, uint8_t fill_g, uint8_t fill_r); + uint8_t fill_b_or_gray = 0, uint8_t fill_g = 0, uint8_t fill_r = 0); /// \brief Extract image channel by index bool ExtractChannel(LiteMat &src, LiteMat &dst, int col); @@ -113,6 +118,25 @@ bool GetAffineTransform(std::vector src_point, std::vector dst_poi /// \brief Matrix transpose bool Transpose(LiteMat &src, LiteMat &dst); +/// \brief Filter the image by a Gaussian kernel +bool GaussianBlur(const LiteMat &src, LiteMat &dst, const std::vector &ksize, double sigmaX, double sigmaY = 0.f, + PaddBorderType pad_type = PaddBorderType::PADD_BORDER_DEFAULT); + +/// \brief Detect edges in an image +bool Canny(const LiteMat &src, LiteMat &dst, double low_thresh, double high_thresh, int ksize = 3, + bool L2gradient = false); + +/// \brief Apply a 2D convolution over the image +bool Conv2D(const LiteMat &src, const LiteMat &kernel, LiteMat &dst, LDataType dst_type, + PaddBorderType pad_type = PaddBorderType::PADD_BORDER_DEFAULT); + +/// \brief Applies a separable linear convolution over the image +bool ConvRowCol(const LiteMat &src, const LiteMat &kx, const LiteMat &ky, LiteMat &dst, LDataType dst_type, + PaddBorderType pad_type = PaddBorderType::PADD_BORDER_DEFAULT); + +/// \brief Filter the image by a Sobel kernel +bool Sobel(const LiteMat &src, LiteMat &dst, int flag_x, int flag_y, int ksize, PaddBorderType pad_type); + } // namespace dataset } // namespace mindspore #endif // IMAGE_PROCESS_H_ diff --git a/tests/ut/cpp/dataset/image_process_test.cc b/tests/ut/cpp/dataset/image_process_test.cc index 96a21aac98..b8baea089f 100644 --- a/tests/ut/cpp/dataset/image_process_test.cc +++ b/tests/ut/cpp/dataset/image_process_test.cc @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - + #include "common/common.h" #include "lite_cv/lite_mat.h" #include "lite_cv/image_process.h" @@ -207,6 +207,29 @@ bool ReadYUV(const char *filename, int w, int h, uint8_t **data) { return true; } +TEST_F(MindDataImageProcess, TestRGBA2GRAY) { + std::string filename = "data/dataset/apple.jpg"; + cv::Mat src_image = cv::imread(filename, cv::ImreadModes::IMREAD_COLOR); + cv::Mat gray_image; + cv::cvtColor(src_image, gray_image, CV_BGR2GRAY); + + cv::Mat rgba_mat; + cv::cvtColor(src_image, rgba_mat, CV_BGR2RGBA); + bool ret = false; + LiteMat lite_mat_gray; + ret = + InitFromPixel(rgba_mat.data, LPixelType::RGBA2GRAY, LDataType::UINT8, rgba_mat.cols, rgba_mat.rows, lite_mat_gray); + ASSERT_TRUE(ret == true); + + double distance = 0.f; + int total_size = gray_image.cols * gray_image.rows * gray_image.channels(); + for (int i = 0; i < total_size; i++) { + distance += pow((uint8_t)gray_image.data[i] - ((uint8_t *)lite_mat_gray)[i], 2); + } + distance = sqrt(distance / total_size); + EXPECT_EQ(distance, 0.0f); +} + TEST_F(MindDataImageProcess, testNV21ToBGR) { // ffmpeg -i ./data/dataset/apple.jpg -s 1024*800 -pix_fmt nv21 ./data/dataset/yuv/test_nv21.yuv const char *filename = "data/dataset/yuv/test_nv21.yuv"; @@ -410,7 +433,6 @@ TEST_F(MindDataImageProcess, TestPadd) { bool ret = InitFromPixel(rgba_mat.data, LPixelType::RGBA2BGR, LDataType::UINT8, rgba_mat.cols, rgba_mat.rows, lite_mat_bgr); ASSERT_TRUE(ret == true); - ASSERT_TRUE(ret == true); LiteMat makeborder; ret = Pad(lite_mat_bgr, makeborder, top, bottom, left, right, PaddBorderType::PADD_BORDER_CONSTANT, 255, 255, 255); ASSERT_TRUE(ret == true); @@ -441,7 +463,6 @@ TEST_F(MindDataImageProcess, TestPadZero) { bool ret = InitFromPixel(rgba_mat.data, LPixelType::RGBA2BGR, LDataType::UINT8, rgba_mat.cols, rgba_mat.rows, lite_mat_bgr); ASSERT_TRUE(ret == true); - ASSERT_TRUE(ret == true); LiteMat makeborder; ret = Pad(lite_mat_bgr, makeborder, top, bottom, left, right, PaddBorderType::PADD_BORDER_CONSTANT, 255, 255, 255); ASSERT_TRUE(ret == true); @@ -454,6 +475,68 @@ TEST_F(MindDataImageProcess, TestPadZero) { EXPECT_EQ(distance, 0.0f); } +TEST_F(MindDataImageProcess, TestPadReplicate) { + std::string filename = "data/dataset/apple.jpg"; + cv::Mat image = cv::imread(filename, cv::ImreadModes::IMREAD_COLOR); + + int left = 20; + int right = 20; + int top = 20; + int bottom = 20; + cv::Mat b_image; + cv::copyMakeBorder(image, b_image, top, bottom, left, right, cv::BORDER_REPLICATE); + + cv::Mat rgba_mat; + cv::cvtColor(image, rgba_mat, CV_BGR2RGBA); + LiteMat lite_mat_bgr; + bool ret = + InitFromPixel(rgba_mat.data, LPixelType::RGBA2BGR, LDataType::UINT8, rgba_mat.cols, rgba_mat.rows, lite_mat_bgr); + ASSERT_TRUE(ret == true); + + LiteMat makeborder; + ret = Pad(lite_mat_bgr, makeborder, top, bottom, left, right, PaddBorderType::PADD_BORDER_REPLICATE); + ASSERT_TRUE(ret == true); + + size_t total_size = makeborder.height_ * makeborder.width_ * makeborder.channel_; + double distance = 0.0f; + for (size_t i = 0; i < total_size; i++) { + distance += pow((uint8_t)b_image.data[i] - ((uint8_t *)makeborder)[i], 2); + } + distance = sqrt(distance / total_size); + EXPECT_EQ(distance, 0.0f); +} + +TEST_F(MindDataImageProcess, TestPadReflect101) { + std::string filename = "data/dataset/apple.jpg"; + cv::Mat image = cv::imread(filename, cv::ImreadModes::IMREAD_COLOR); + + int left = 20; + int right = 20; + int top = 20; + int bottom = 20; + cv::Mat b_image; + cv::copyMakeBorder(image, b_image, top, bottom, left, right, cv::BORDER_REFLECT_101); + + cv::Mat rgba_mat; + cv::cvtColor(image, rgba_mat, CV_BGR2RGBA); + LiteMat lite_mat_bgr; + bool ret = + InitFromPixel(rgba_mat.data, LPixelType::RGBA2BGR, LDataType::UINT8, rgba_mat.cols, rgba_mat.rows, lite_mat_bgr); + ASSERT_TRUE(ret == true); + + LiteMat makeborder; + ret = Pad(lite_mat_bgr, makeborder, top, bottom, left, right, PaddBorderType::PADD_BORDER_REFLECT_101); + ASSERT_TRUE(ret == true); + + size_t total_size = makeborder.height_ * makeborder.width_ * makeborder.channel_; + double distance = 0.0f; + for (size_t i = 0; i < total_size; i++) { + distance += pow((uint8_t)b_image.data[i] - ((uint8_t *)makeborder)[i], 2); + } + distance = sqrt(distance / total_size); + EXPECT_EQ(distance, 0.0f); +} + TEST_F(MindDataImageProcess, TestGetDefaultBoxes) { std::string benchmark = "data/dataset/testLite/default_boxes.bin"; BoxesConfig config; @@ -1279,3 +1362,355 @@ TEST_F(MindDataImageProcess, testGetAffineTransform) { EXPECT_TRUE(ret); AccuracyComparison(expect_matrix, M); } + +TEST_F(MindDataImageProcess, TestConv2D8U) { + LiteMat lite_mat_src; + lite_mat_src.Init(3, 3, 1, LDataType::UINT8); + uint8_t *src_ptr = lite_mat_src; + for (int i = 0; i < 9; i++) { + src_ptr[i] = i % 3; + } + LiteMat kernel; + kernel.Init(3, 3, 1, LDataType::FLOAT32); + float *kernel_ptr = kernel; + for (int i = 0; i < 9; i++) { + kernel_ptr[i] = i % 2; + } + LiteMat lite_mat_dst; + bool ret = Conv2D(lite_mat_src, kernel, lite_mat_dst, LDataType::UINT8); + ASSERT_TRUE(ret == true); + + std::vector expected_result = {2, 4, 6, 2, 4, 6, 2, 4, 6}; + + size_t total_size = lite_mat_dst.height_ * lite_mat_dst.width_ * lite_mat_dst.channel_; + float distance = 0.0f; + for (size_t i = 0; i < total_size; i++) { + distance += pow(((uint8_t *)lite_mat_dst)[i] - expected_result[i], 2); + } + distance = sqrt(distance / total_size); + EXPECT_EQ(distance, 0.0f); +} + +TEST_F(MindDataImageProcess, TestConv2D32F) { + LiteMat lite_mat_src; + lite_mat_src.Init(2, 2, 1, LDataType::FLOAT32); + float *src_ptr = lite_mat_src; + for (int i = 0; i < 4; i++) { + src_ptr[i] = static_cast(i) / 2; + } + LiteMat kernel; + kernel.Init(2, 2, 1, LDataType::FLOAT32); + float *kernel_ptr = kernel; + for (int i = 0; i < 4; i++) { + kernel_ptr[i] = static_cast(i); + } + LiteMat lite_mat_dst; + bool ret = Conv2D(lite_mat_src, kernel, lite_mat_dst, LDataType::FLOAT32); + ASSERT_TRUE(ret == true); + + std::vector expected_result = {2.f, 3.f, 6.f, 7.f}; + + size_t total_size = lite_mat_dst.height_ * lite_mat_dst.width_ * lite_mat_dst.channel_; + float distance = 0.0f; + for (size_t i = 0; i < total_size; i++) { + distance += pow(((float *)lite_mat_dst)[i] - expected_result[i], 2); + } + distance = sqrt(distance / total_size); + EXPECT_EQ(distance, 0.0f); +} + +TEST_F(MindDataImageProcess, TestGaussianBlurSize35) { + std::string filename = "data/dataset/apple.jpg"; + cv::Mat src_image = cv::imread(filename, cv::ImreadModes::IMREAD_COLOR); + + cv::Mat dst_image; + cv::GaussianBlur(src_image, dst_image, cv::Size(3, 5), 3, 3); + + cv::Mat rgba_mat; + cv::cvtColor(src_image, rgba_mat, CV_BGR2RGBA); + + LiteMat lite_mat_bgr; + bool ret = + InitFromPixel(rgba_mat.data, LPixelType::RGBA2BGR, LDataType::UINT8, rgba_mat.cols, rgba_mat.rows, lite_mat_bgr); + ASSERT_TRUE(ret == true); + + LiteMat lite_mat_dst; + ret = GaussianBlur(lite_mat_bgr, lite_mat_dst, {3, 5}, 3, 3); + ASSERT_TRUE(ret == true); + + size_t total_size = lite_mat_dst.height_ * lite_mat_dst.width_ * lite_mat_dst.channel_; + double distance = 0.0f; + for (size_t i = 0; i < total_size; i++) { + distance += pow((uint8_t)dst_image.data[i] - ((uint8_t *)lite_mat_dst)[i], 2); + } + distance = sqrt(distance / total_size); + EXPECT_LE(distance, 1.0f); +} + +TEST_F(MindDataImageProcess, TestGaussianBlurSize13) { + std::string filename = "data/dataset/apple.jpg"; + cv::Mat src_image = cv::imread(filename, cv::ImreadModes::IMREAD_COLOR); + + cv::Mat dst_image; + cv::GaussianBlur(src_image, dst_image, cv::Size(1, 3), 3); + + cv::Mat rgba_mat; + cv::cvtColor(src_image, rgba_mat, CV_BGR2RGBA); + + LiteMat lite_mat_bgr; + bool ret = + InitFromPixel(rgba_mat.data, LPixelType::RGBA2BGR, LDataType::UINT8, rgba_mat.cols, rgba_mat.rows, lite_mat_bgr); + ASSERT_TRUE(ret == true); + + LiteMat lite_mat_dst; + ret = GaussianBlur(lite_mat_bgr, lite_mat_dst, {1, 3}, 3); + ASSERT_TRUE(ret == true); + + size_t total_size = lite_mat_dst.height_ * lite_mat_dst.width_ * lite_mat_dst.channel_; + double distance = 0.0f; + for (size_t i = 0; i < total_size; i++) { + distance += pow((uint8_t)dst_image.data[i] - ((uint8_t *)lite_mat_dst)[i], 2); + } + distance = sqrt(distance / total_size); + EXPECT_LE(distance, 1.0f); +} + +TEST_F(MindDataImageProcess, TestGaussianBlurInvalidParams) { + std::string filename = "data/dataset/apple.jpg"; + cv::Mat src_image = cv::imread(filename, cv::ImreadModes::IMREAD_COLOR); + cv::Mat rgba_mat; + cv::cvtColor(src_image, rgba_mat, CV_BGR2RGBA); + + LiteMat lite_mat_bgr; + bool ret = + InitFromPixel(rgba_mat.data, LPixelType::RGBA2BGR, LDataType::UINT8, rgba_mat.cols, rgba_mat.rows, lite_mat_bgr); + ASSERT_TRUE(ret == true); + + LiteMat lite_mat_dst; + + // even size + ret = GaussianBlur(lite_mat_bgr, lite_mat_dst, {3, 4}, 3); + ASSERT_TRUE(ret == false); + + // ksize.size() != 2 + ret = GaussianBlur(lite_mat_bgr, lite_mat_dst, {3, 4, 5}, 3); + ASSERT_TRUE(ret == false); + + // size less or equal to 0 + ret = GaussianBlur(lite_mat_bgr, lite_mat_dst, {0, 3}, 3); + ASSERT_TRUE(ret == false); + + // sigmaX less or equal to 0 + ret = GaussianBlur(lite_mat_bgr, lite_mat_dst, {3, 3}, 0); + ASSERT_TRUE(ret == false); +} + +TEST_F(MindDataImageProcess, TestCannySize3) { + std::string filename = "data/dataset/apple.jpg"; + cv::Mat src_image = cv::imread(filename, cv::ImreadModes::IMREAD_COLOR); + cv::Mat gray_image; + cv::cvtColor(src_image, gray_image, CV_BGR2GRAY); + cv::Mat dst_image; + cv::Canny(gray_image, dst_image, 100, 200, 3); + + cv::Mat rgba_mat; + cv::cvtColor(src_image, rgba_mat, CV_BGR2RGBA); + bool ret = false; + LiteMat lite_mat_gray; + ret = + InitFromPixel(rgba_mat.data, LPixelType::RGBA2GRAY, LDataType::UINT8, rgba_mat.cols, rgba_mat.rows, lite_mat_gray); + ASSERT_TRUE(ret == true); + + LiteMat lite_mat_dst; + ret = Canny(lite_mat_gray, lite_mat_dst, 100, 200, 3); + ASSERT_TRUE(ret == true); + + int total_size = lite_mat_dst.height_ * lite_mat_dst.width_ * lite_mat_dst.channel_; + double distance = 0.0f; + for (int i = 0; i < total_size; i++) { + distance += pow((uint8_t)dst_image.data[i] - ((uint8_t *)lite_mat_dst)[i], 2); + } + distance = sqrt(distance / total_size); + EXPECT_EQ(distance, 0.0f); +} + +TEST_F(MindDataImageProcess, TestCannySize5) { + std::string filename = "data/dataset/apple.jpg"; + cv::Mat src_image = cv::imread(filename, cv::ImreadModes::IMREAD_COLOR); + cv::Mat gray_image; + cv::cvtColor(src_image, gray_image, CV_BGR2GRAY); + cv::Mat dst_image; + cv::Canny(gray_image, dst_image, 200, 300, 5); + + cv::Mat rgba_mat; + cv::cvtColor(src_image, rgba_mat, CV_BGR2RGBA); + bool ret = false; + LiteMat lite_mat_gray; + ret = + InitFromPixel(rgba_mat.data, LPixelType::RGBA2GRAY, LDataType::UINT8, rgba_mat.cols, rgba_mat.rows, lite_mat_gray); + ASSERT_TRUE(ret == true); + + LiteMat lite_mat_dst; + ret = Canny(lite_mat_gray, lite_mat_dst, 200, 300, 5); + ASSERT_TRUE(ret == true); + + int total_size = lite_mat_dst.height_ * lite_mat_dst.width_ * lite_mat_dst.channel_; + double distance = 0.0f; + for (int i = 0; i < total_size; i++) { + distance += pow((uint8_t)dst_image.data[i] - ((uint8_t *)lite_mat_dst)[i], 2); + } + distance = sqrt(distance / total_size); + EXPECT_EQ(distance, 0.0f); +} + +TEST_F(MindDataImageProcess, TestCannyL2) { + std::string filename = "data/dataset/apple.jpg"; + cv::Mat src_image = cv::imread(filename, cv::ImreadModes::IMREAD_COLOR); + cv::Mat gray_image; + cv::cvtColor(src_image, gray_image, CV_BGR2GRAY); + cv::Mat dst_image; + cv::Canny(gray_image, dst_image, 50, 150, 3, true); + + cv::Mat rgba_mat; + cv::cvtColor(src_image, rgba_mat, CV_BGR2RGBA); + bool ret = false; + LiteMat lite_mat_gray; + ret = + InitFromPixel(rgba_mat.data, LPixelType::RGBA2GRAY, LDataType::UINT8, rgba_mat.cols, rgba_mat.rows, lite_mat_gray); + ASSERT_TRUE(ret == true); + + LiteMat lite_mat_dst; + ret = Canny(lite_mat_gray, lite_mat_dst, 50, 150, 3, true); + ASSERT_TRUE(ret == true); + + int total_size = lite_mat_dst.height_ * lite_mat_dst.width_ * lite_mat_dst.channel_; + double distance = 0.0f; + for (int i = 0; i < total_size; i++) { + distance += pow((uint8_t)dst_image.data[i] - ((uint8_t *)lite_mat_dst)[i], 2); + } + + distance = sqrt(distance / total_size); + EXPECT_EQ(distance, 0.0f); +} + +TEST_F(MindDataImageProcess, TestCannyInvalidParams) { + std::string filename = "data/dataset/apple.jpg"; + cv::Mat src_image = cv::imread(filename, cv::ImreadModes::IMREAD_COLOR); + + cv::Mat rgba_mat; + cv::cvtColor(src_image, rgba_mat, CV_BGR2RGBA); + + bool ret = false; + LiteMat lite_mat_bgr; + ret = + InitFromPixel(rgba_mat.data, LPixelType::RGBA2BGR, LDataType::UINT8, rgba_mat.cols, rgba_mat.rows, lite_mat_bgr); + ASSERT_TRUE(ret == true); + + // channel is not 1 + LiteMat lite_mat_dst; + ret = Canny(lite_mat_bgr, lite_mat_dst, 70, 210, 3); + ASSERT_TRUE(ret == false); + + LiteMat lite_mat_gray; + ret = + InitFromPixel(rgba_mat.data, LPixelType::RGBA2GRAY, LDataType::UINT8, rgba_mat.cols, rgba_mat.rows, lite_mat_gray); + ASSERT_TRUE(ret == true); + + // low_thresh less than 0 + ret = Canny(lite_mat_gray, lite_mat_dst, -5, 230, 3); + ASSERT_TRUE(ret == false); + + // high_thresh less than low_thresh + ret = Canny(lite_mat_gray, lite_mat_dst, 250, 130, 3); + ASSERT_TRUE(ret == false); + + // even size + ret = Canny(lite_mat_gray, lite_mat_dst, 60, 180, 4); + ASSERT_TRUE(ret == false); + + // size less than 3 or large than 7 + ret = Canny(lite_mat_gray, lite_mat_dst, 10, 190, 9); + ASSERT_TRUE(ret == false); +} + +TEST_F(MindDataImageProcess, TestSobel) { + std::string filename = "data/dataset/apple.jpg"; + cv::Mat src_image = cv::imread(filename, cv::ImreadModes::IMREAD_COLOR); + cv::Mat gray_image; + cv::cvtColor(src_image, gray_image, CV_BGR2GRAY); + + cv::Mat sobel_image_x; + cv::Mat sobel_image_y; + cv::Sobel(gray_image, sobel_image_x, CV_32F, 1, 0, 3, 1, 0, cv::BORDER_REPLICATE); + cv::Sobel(gray_image, sobel_image_y, CV_32F, 0, 1, 3, 1, 0, cv::BORDER_REPLICATE); + + cv::Mat sobel_cv_x, sobel_cv_y; + sobel_image_x.convertTo(sobel_cv_x, CV_8UC1); + sobel_image_y.convertTo(sobel_cv_y, CV_8UC1); + + cv::Mat rgba_mat; + cv::cvtColor(src_image, rgba_mat, CV_BGR2RGBA); + bool ret = false; + LiteMat lite_mat_gray; + ret = + InitFromPixel(rgba_mat.data, LPixelType::RGBA2GRAY, LDataType::UINT8, rgba_mat.cols, rgba_mat.rows, lite_mat_gray); + ASSERT_TRUE(ret == true); + LiteMat lite_mat_x; + LiteMat lite_mat_y; + Sobel(lite_mat_gray, lite_mat_x, 1, 0, 3, PaddBorderType::PADD_BORDER_REPLICATE); + Sobel(lite_mat_gray, lite_mat_y, 0, 1, 3, PaddBorderType::PADD_BORDER_REPLICATE); + ASSERT_TRUE(ret == true); + + cv::Mat dst_imageX(lite_mat_x.height_, lite_mat_x.width_, CV_32FC1, lite_mat_x.data_ptr_); + cv::Mat dst_imageY(lite_mat_y.height_, lite_mat_y.width_, CV_32FC1, lite_mat_y.data_ptr_); + cv::Mat sobel_ms_x, sobel_ms_y; + dst_imageX.convertTo(sobel_ms_x, CV_8UC1); + dst_imageY.convertTo(sobel_ms_y, CV_8UC1); + + size_t total_size = lite_mat_x.height_ * lite_mat_x.width_ * lite_mat_x.channel_; + float distance_x = 0.0f, distance_y = 0.0f; + for (int i = 0; i < total_size; i++) { + distance_x += pow((uint8_t)sobel_cv_x.data[i] - (uint8_t)sobel_ms_x.data[i], 2); + distance_y += pow((uint8_t)sobel_cv_y.data[i] - (uint8_t)sobel_ms_y.data[i], 2); + } + distance_x = sqrt(distance_x / total_size); + distance_y = sqrt(distance_y / total_size); + EXPECT_EQ(distance_x, 0.0f); + EXPECT_EQ(distance_y, 0.0f); +} + +TEST_F(MindDataImageProcess, TestSobelFlag) { + std::string filename = "data/dataset/apple.jpg"; + cv::Mat src_image = cv::imread(filename, cv::ImreadModes::IMREAD_COLOR); + cv::Mat gray_image; + cv::cvtColor(src_image, gray_image, CV_BGR2GRAY); + + cv::Mat sobel_image_x; + cv::Sobel(gray_image, sobel_image_x, CV_32F, 3, 1, 5, 1, 0, cv::BORDER_REPLICATE); + + cv::Mat sobel_cv_x; + sobel_image_x.convertTo(sobel_cv_x, CV_8UC1); + + cv::Mat rgba_mat; + cv::cvtColor(src_image, rgba_mat, CV_BGR2RGBA); + bool ret = false; + LiteMat lite_mat_gray; + ret = + InitFromPixel(rgba_mat.data, LPixelType::RGBA2GRAY, LDataType::UINT8, rgba_mat.cols, rgba_mat.rows, lite_mat_gray); + ASSERT_TRUE(ret == true); + LiteMat lite_mat_x; + Sobel(lite_mat_gray, lite_mat_x, 3, 1, 5, PaddBorderType::PADD_BORDER_REPLICATE); + ASSERT_TRUE(ret == true); + + cv::Mat dst_imageX(lite_mat_x.height_, lite_mat_x.width_, CV_32FC1, lite_mat_x.data_ptr_); + cv::Mat sobel_ms_x; + dst_imageX.convertTo(sobel_ms_x, CV_8UC1); + + size_t total_size = lite_mat_x.height_ * lite_mat_x.width_ * lite_mat_x.channel_; + float distance_x = 0.0f; + for (int i = 0; i < total_size; i++) { + distance_x += pow((uint8_t)sobel_cv_x.data[i] - (uint8_t)sobel_ms_x.data[i], 2); + } + distance_x = sqrt(distance_x / total_size); + EXPECT_EQ(distance_x, 0.0f); +}