百度飞桨-基于CV的工业读表案例(修改读表范围和数值)

外面的项目需要做一个工业读表的功能,并给发来了百度飞桨的案例链接https://www.paddlepaddle.org.cn/tutorials/projectdetail/3387933。基本的要求是在百度飞桨的基础上进行修改即可,因此主要的工作是修改读表的部分。
由于百度的教程非常详细,所以代码配置问题在此处省略,需要注意的是环境配置必须完全按照教程使用CUDA10.2,否则会导致编译错误,此处只对如何修改读表做出总结。

修改表盘读取范围

打开meter_reader.cpp可以看到63行处有GetMeterReading方法,ctrl+左键点入该方法的所在位置,是在src/reader_postprocess.cpp文件的第233行,而此处也正如教程开始的图片所示。
流程图

// reader_postprocess.cpp    line 233

bool GetMeterReading(
  const std::vector<std::vector<uint8_t>> &seg_label_maps,
  std::vector<float> *readings) {
  for (auto i = 0; i < seg_label_maps.size(); i++) {
    std::vector<uint8_t> rectangle_meter;
    CircleToRectangle(seg_label_maps[i], &rectangle_meter);  // 环形表盘转为矩形

    std::vector<int> line_scale;
    std::vector<int> line_pointer;
    RectangleToLine(rectangle_meter, &line_scale, &line_pointer);  // 二维图像转一维数组

    std::vector<int> binaried_scale;
    MeanBinarization(line_scale, &binaried_scale);
    std::vector<int> binaried_pointer;
    MeanBinarization(line_pointer, &binaried_pointer);  // 对刻度用均值做二值化

    std::vector<float> scale_location;
    LocateScale(binaried_scale, &scale_location);

    float pointer_location;
    LocatePointer(binaried_pointer, &pointer_location);

    MeterResult result;
    GetRelativeLocation(
      scale_location, pointer_location, &result);  // 定位指针相对刻度的位置

    float reading;
    CalculateReading(result, &reading);  // 根据刻度的根数判断表盘类型和量程,计算读数
    readings->push_back(reading);
  }
  return true;
}

首先将语义分割的结果经过腐蚀处理后输入,之后使用CircleToRectangle将环形表盘展开为矩形图像,这个方法是修改的关键,因为涉及到这个矩形是从何处开始截取的,即表盘的起始点。该方法在前面的57行:

// reader_postprocess.cpp    line 57

bool CircleToRectangle(
  const std::vector<uint8_t> &seg_label_map,
  std::vector<uint8_t> *rectangle_meter) {
  float theta;
  int rho;
  int image_x;
  int image_y;
  // The minimum scale value is at the bottom left, the maximum scale value
  // is at the bottom right, so the vertical down axis is the starting axis and
  // rotates around the meter ceneter counterclockwise.(Actually it is clockwise)
  // 此处注释也非常清晰,最小刻度值在左下角,最大刻度值在右下角,所以垂直下轴为起始轴,绕仪表中心逆时针旋转(谷歌翻译)。(实际上是顺时针,且代码提供的是顺时针,此处为标注错误)
  *rectangle_meter =
    std::vector<uint8_t> (RECTANGLE_WIDTH * RECTANGLE_HEIGHT, 0);
  for (int row = 0; row < RECTANGLE_HEIGHT; row++) {
    for (int col = 0; col < RECTANGLE_WIDTH; col++) {
      // 从for循环可以看出,theta是从0-360的,而rho为设定圆周-row,即从最外圈向圆心遍历。
      theta = PI * 2 / RECTANGLE_WIDTH * (col + 1);
      rho = CIRCLE_RADIUS - row - 1;
      // 此处对应了图像的y,x坐标,值得注意的是图片的坐标系通常为top-left即左上角,向右为x轴向下为y轴
      // 再次点入CIRCLE_CENTER,会发现在meter_config.cpp中存放了许多信息,在下个代码块会进行标注
      // 重点关注下面两行代码,是修改读表起始点和旋转方向的关键
      int y = static_cast<int>(CIRCLE_CENTER[0] + rho * cos(theta) + 0.5);
      int x = static_cast<int>(CIRCLE_CENTER[1] - rho * sin(theta) + 0.5);
      (*rectangle_meter)[row * RECTANGLE_WIDTH + col] =
        seg_label_map[y * METER_SHAPE[1] + x];
    }
  }
  return true;
}

如果想要将表盘起始点改为数值向上,只需要将y坐标改为:

int y = static_cast<int>(CIRCLE_CENTER[0] - rho * cos(theta) + 0.5);
int x = static_cast<int>(CIRCLE_CENTER[1] + rho * sin(theta) + 0.5); 

使得y坐标由小变大再变小,x坐标先变大再变小,自绘示例图对照图片的坐标系便很好理解。
示意图

修改读表数值

在修改完读表范围后,读表数值的修改相对简单一些,首先下面是对meter_config.cpp的标注,后续的更改也是基于这个文件。

// meter_config.cpp

#include "meter_reader/include/meter_config.h"
// METER_SHAPE为图像处理后的大小,与meter_pipeline中resize的尺寸需要对应
std::vector<int> METER_SHAPE = {512, 512};  // height x width
// CIRCLE_CENTER为表盘中心点,由于目标检测的精度极高,因此为METER_SHAPE中心点即可
std::vector<int> CIRCLE_CENTER = {256, 256};
// CIRCLE_RADIUS决定了表盘读取的最外圈,如果刻度距离表外径较远则需要调整,通常来说只需要与圆心相加略小于尺寸即可
int CIRCLE_RADIUS = 250;
float PI = 3.1415926536;
// 以下两项为表盘转为矩形的尺寸,比较重要的是HEIGHT,决定了从最外圈到圆心的像素数量,从而截取有刻度的圆环
// WIDTH则决定了360度被分成多少份读取,即转换的精度,但由于像素点是有限的,所以并非越大越好
int RECTANGLE_HEIGHT = 120;
int RECTANGLE_WIDTH = 1570;
// 下面是如果需要检测不同的表盘,如果其刻度线根数相差较大,可以通过修改这个添加检测的表盘数量
// TYPE_THRESHOLD为刻度线根数的阈值,而METER_CONFIG存放了表盘的分度值和量程
int TYPE_THRESHOLD = 40;
// std::vector<int> TYPE_THRESHOLD = {30, 50, 80};
std::vector<MeterConfig> METER_CONFIG = {
  MeterConfig(25.0f/50.0f, 25.0f, "(MPa)"),
  MeterConfig(1.6f/32.0f,  1.6f,   "(MPa)"),
  MeterConfig(100.0f/100.0f, 100.0f, "(Kg)")
};
// 语义分割的标签
std::map<std::string, uint8_t> SEG_CNAME2CLSID = {
  {"background", 0}, {"pointer", 1}, {"scale", 2}
};

根据GetMeterReading方法可知,CalculateReading即最终读表的数值,在reader_postprocess.cpp的201行。

// reader_postprocess.cpp	line 201

bool CalculateReading(const MeterResult &result,
                      float *reading) {
  // Provide a digital readout according to point location relative
  // to the scales
  if (result.num_scales_ > TYPE_THRESHOLD) {
    *reading = result.pointed_scale_ * METER_CONFIG[0].scale_interval_value_;
  } else {
    *reading = result.pointed_scale_ * METER_CONFIG[1].scale_interval_value_;
  }
  return true;

此处TYPE_THRESHOLD为刻度线根数,如果有多个不同类型的表盘则至少要保证其刻度线根数不一样,从而可以添加多个TYPE_THRESHOLD,并添加多种类型的表盘。如:

bool CalculateReading(const MeterResult &result,
                      float *reading) {
  if (result.num_scales_ > TYPE_THRESHOLD[0] && result.num_scales_ < TYPE_THRESHOLD[1]) {
    *reading = result.pointed_scale_ * METER_CONFIG[0].scale_interval_value_;
  } else if (result.num_scales_ > TYPE_THRESHOLD[1] && result.num_scales_ < TYPE_THRESHOLD[2]){
    *reading = result.pointed_scale_ * METER_CONFIG[1].scale_interval_value_;
  } else {
    *reading = result.pointed_scale_ * METER_CONFIG[2].scale_interval_value_;
  }
  return true;

小结

得益于百度飞桨内训练好的目标检测和语义分割模型,使得工作的复杂度大大降低。如果需要读取的表盘和刻度与案例相差甚远,则需要重新训练检测和语义分割的模型。此外,该方案需要保证表盘的清晰可见,使用网络高噪点图像做测试时,该方法的读取效果并不佳。
基于该案例做提升的方法有许多,如利用OCR自动获取新表盘的最大量程,使用较小的检测模型进行目标检测以提升速度,通过TCP传输读数做进一步处理等,本文就不再赘述了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

帕里亚

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值