【算法部署】深度学习图像前处理C++实现


前言

使用C++进行算法部署,将图像传入模型时,要对图像做一些变换,使其满足推理框架支持的图像数据格式。本文总结常用处理方法及其C++代码实现。


一、标准化处理

深度学习推理时,需要将图像像素值缩放到[0, 1]之间,并做标准化处理。

代码实现:

class NormalizeImage :{
   
public:
	// TODO: 根据自己的模型数据需求添加构造函数
	// ......
    void Run(cv::Mat* im, ImageBlob* data);
private:
    // CHW or HWC
    std::vector<float> mean_;
    std::vector<float> scale_;
    bool is_scale_ = true;
    std::string norm_type_ = "mean_std";
};

void NormalizeImage::Run(cv::Mat* im, ImageBlob* data) 
{
   
 double e = 1.0;
 if (is_scale_) {
   
     e /= 255.0;
 }
 (*im).convertTo(*im, CV_32FC3, e);  // 转化成3通道float32
 if (norm_type_ == "mean_std") {
   
     for (int h = 0; h < im->rows; h++) {
   
         for (int w = 0; w < im->cols; w++) {
   
             im->at<cv::Vec3f>(h, w)[0] =
                 (im->at<cv::Vec3f>(h, w)[0] - mean_[0]) / scale_[0];
             im->at<cv::Vec3f>(h, w)[1] =
                 (im->at<cv::Vec3f>(h, w)[1] - mean_[1]) / scale_[1];
             im->at<cv::Vec3f>(h, w)[2] =
                 (im->at<cv::Vec3f>(h, w)[2] - mean_[2]) / scale_[2];
         }
     }
 }
}

二、Resize

resize将推理图像,缩放到模型所支持的尺寸大小。根据需求,可以设定是否将图像按比例缩放。

class Resize :{
   
public:
	// TODO: 根据自己的模型数据需求添加构造函数
	// ......
	
    // Compute best resize scale for x-dimension, y-dimension
    std::pair<float, float> GenerateScale(const cv::Mat& im);

    void Run(cv::Mat* im, ImageBlob* data);

private:
    int interp_;
    bool keep_ratio_;  // 是否保持原图比例进行缩放
    std::vector<int> target_size_;
    std::vector<int> in_net_shape_;
};

void Resize::Run(cv::Mat* im, ImageBlob* data) {
   
   auto resize_scale = GenerateScale(*im);
   cv::resize(
       *im, *im, cv::Size(), resize_scale.first, resize_scale.second, interp_);

   data->in_net_shape_ = {
    static_cast<float>(im->rows),
                          static_cast<float>(im->cols) };
   data->im_shape_ = {
   
       static_cast<float>(im->rows), static_cast<float>(im->cols),
   };
   data->scale_factor_ = {
   
       resize_scale.second, resize_scale.first,
   };
}

std::pair<float, float> Resize::GenerateScale(const cv::Mat& im) {
   
   std::pair<float, float> resize_scale;
   int origin_w = im.cols;
   int origin_h = im.rows;

   if (keep_ratio_) {
   
       int im_size_max = std::max(origin_w, origin_h);
       int im_size_min = std::min(origin_w, origin_h);
       int target_size_max =
           *std::max_element(target_size_.begin(), target_size_.end());
       int target_size_min =
           *std::min_element(target_size_.begin(), target_size_.end());
       float scale_min =
           static_cast<float>(target_size_min) / static_cast<float>(im_size_min);
       float scale_max =
           static_cast<float>(target_size_max) / static_cast<float>(im_size_max);
       float scale_ratio = std::min(scale_min, scale_max);  // 以最小值作为缩放因子
       resize_scale = {
    scale_ratio, scale_ratio };
   }
   else {
   
       resize_scale.first = static_cast < float>(2.0);
       resize_scale.second = static_cast <float>(2.0);

   }
   return resize_scale;
}

三、LetterBoxResize

LetterBoxResize将图像按照原图比例缩放,然后短边用常数填充,使其尺寸达到模型所要求的大小。
代码示例:

class LetterBoxResize : {
   
public:
	// TODO: 根据自己的模型数据需求添加构造函数
	// ......
	
    void Init(const YAML::Node& item) {
   
        target_size_ = item["target_size"].as<std::vector<int>>();
    }

    float GenerateScale(const cv::Mat& im);

    virtual void Run(cv::Mat* im, ImageBlob* data);

private:
    std::vector<int> target_size_;
    std::vector<int> in_net_shape_;
};

void LetterBoxResize::Run(cv::Mat* im, ImageBlob* data) {
   
    float resize_scale = GenerateScale(*im);  // 保持比例缩放
    int new_shape_w = std::round(im->cols * resize_scale);
    int new_shape_h = std::round(im->rows * resize_scale);
    data->im_shape_ = {
    static_cast<float>(new_shape_h),
                       static_cast<float>(new_shape_w) };
    float padw = (target_size_[1] - new_shape_w) / 2.;
    float padh = (target_size_[0] - new_shape_h) / 2.;

    int top = std::round(padh - 0.1);
    int bottom = std::round(padh + 0.1);
    int left = std::round(padw - 0.1);
    int right = std::round(padw + 0.1);

    cv::resize(
        *im, *im, cv::Size(new_shape_w, new_shape_h), 0, 0, cv::INTER_AREA);

    data->in_net_shape_ = {
   
        static_cast<float>(im->rows), static_cast<float>(im->cols),
    };
    cv::copyMakeBorder(*im,
        *im,
        top,
        bottom,
        left,
        right,
        cv::BORDER_CONSTANT,
        cv::Scalar(127.5));

    data->in_net_shape_ = {
   
        static_cast<float>(im->rows), static_cast<float>(im->cols),
    };

    data->scale_factor_ = {
   
        resize_scale, resize_scale,
    };
}

float LetterBoxResize::GenerateScale(const cv::Mat& im) {
   
    int origin_w = im.cols;
    int origin_h = im.rows;

    int target_h = target_size_[0];
    int target_w = target_size_[1];

    float ratio_h = static_cast<float>(target_h) / static_cast<float>(origin_h);
    float ratio_w = static_cast<float>(target_w) / static_cast<float>(origin_w);
    float resize_scale = std::min(ratio_h, ratio_w);
    return resize_scale;
}

四、仿射变换

对待推理图像做仿射变换。

class WarpAffine : public PreprocessOp {
   
public:
	// TODO: 根据自己的模型数据需求添加构造函数
	// ......

    void Run(cv::Mat* im, ImageBlob* data);

private:
    int input_h_;
    int input_w_;
    int interp_ = 1;
    bool keep_res_ = true;
    int pad_ = 31;
};

void GetAffineTrans(const cv::Point2f center,
    const cv::Point2f input_size,
    const cv::Point2f output_size,
    cv::Mat* trans) {
   
    cv::Point2f srcTri[3];
    cv::Point2f dstTri[3];
    float src_w = input_size.x;
    float dst_w = output_size.x;
    float dst_h = output_size.y;

    cv::Point2f src_dir(0, -0.5 * src_w);
    cv::Point2f dst_dir(0, -0.5 * dst_w);

    srcTri[0] = center;
    srcTri[1] = center + src_dir;
    cv::Point2f src_d = srcTri[0] - srcTri[1];
    srcTri[2] = srcTri[1] + cv::Point2f(-src_d.y, src_d.x);

    dstTri[0] = cv::Point2f(dst_w * 0.5, dst_h * 0.5);
    dstTri[1] = cv::Point2f(dst_w * 0.5, dst_h * 0.5) + dst_dir;
    cv::Point2f dst_d = dstTri[0] - dstTri[1];
    dstTri[2] = dstTri[1] + cv::Point2f(-dst_d.y, dst_d.x);

    *trans = cv::getAffineTransform(srcTri, dstTri);
}

void WarpAffine::Run(cv::Mat* im, ImageBlob* data) {
   
    cv::cvtColor(*im, *im, cv::COLOR_RGB2BGR);
    cv::Mat trans(2, 3, CV_32FC1);
    cv::Point2f center;
    cv::Point2f input_size;
    int h = im->rows;
    int w = im->cols;
    if (keep_res_) {
   
        input_h_ = (h | pad_) + 1;
        input_w_ = (w + pad_) + 1;
        input_size = cv::Point2f(input_w_, input_h_);
        center = cv::Point2f(w / 2, h / 2);
    }
    else {
   
        float s = std::max(h, w) * 1.0;
        input_size = cv::Point2f(s, s);
        center = cv::Point2f(w / 2., h / 2.);
    }
    cv::Point2f output_size(input_w_, input_h_);

    GetAffineTrans(center, input_size, output_size, &trans);
    cv::warpAffine(*im, *im, trans, cv::Size(input_w_, input_h_));
    data->in_net_shape_ = {
   
        static_cast<float>(input_h_), static_cast<float>(input_w_),
    };
}

五、Permute

根据onnxruntime等常用的推理框架的要求,要生成tensor,需要将图像数据根据通道,储存到一段连续的内存中。使用cv::extractChannel可以根据通道,将数据抽取一段内存中。

class Permute : {
   
public:
    void Run(cv::Mat* im, ImageBlob* data);
};

void Permute::Run(cv::Mat* im, ImageBlob* data) {
   
    (*im).convertTo(*im, CV_32FC3);
    int rh = im->rows;
    int rw = im->cols;
    int rc = im->channels();
    (data->im_data_).resize(rc * rh * rw);
    float* base = (data->im_data_).data();
    for (int i = 0; i < rc; ++i) {
   
        cv::extractChannel(*im, cv::Mat(rh, rw, CV_32FC1, base + i * rh * rw), i);
    } // 根据通道将图像数据抽取到内存中,用于生成tensor
}

最近更新

  1. TCP协议是安全的吗?

    2024-01-06 17:52:02       18 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-01-06 17:52:02       19 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-01-06 17:52:02       19 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-01-06 17:52:02       20 阅读

热门阅读

  1. Doris 数仓使用规范(经验版)

    2024-01-06 17:52:02       73 阅读
  2. 鸡兔同笼问题加强版

    2024-01-06 17:52:02       35 阅读
  3. Mysql 查看表注释或字段注释

    2024-01-06 17:52:02       40 阅读
  4. Flutter GetX 之 路由管理

    2024-01-06 17:52:02       55 阅读
  5. openssl ans1定义的实体

    2024-01-06 17:52:02       27 阅读
  6. 【leetcode100-031】【链表】k个一组翻转链表

    2024-01-06 17:52:02       39 阅读
  7. SRS服务器RTMP2WebRTC外网拉流配置

    2024-01-06 17:52:02       50 阅读
  8. LeetCode[62] 不同路径

    2024-01-06 17:52:02       32 阅读
  9. react:ffcreator中FFCreatorCenter视频队例

    2024-01-06 17:52:02       36 阅读
  10. 【注解】@FeignClient 用于微服务通信

    2024-01-06 17:52:02       31 阅读
  11. 树莓派4B 入门

    2024-01-06 17:52:02       42 阅读
  12. react高阶成分(HOC)

    2024-01-06 17:52:02       45 阅读
  13. 写你的第一个Vue程序

    2024-01-06 17:52:02       43 阅读