【问题标题】:Fastest way to calculate 'percentage matched' of two trapezoids计算两个梯形的“匹配百分比”的最快方法
【发布时间】:2016-10-30 20:35:56
【问题描述】:

我问了以下问题here 并得到了一个很好的解决方案,但发现它的性能太慢(640x480 图像需要 2-300 毫秒)。现在我想考虑如何优化它。

问题:
给定两个多边形(总是平行于 X 轴的梯形),我想通过某种方式计算它们匹配的程度。我的意思是,重叠区域是不够的,因为如果一个多边形有多余的区域,那么就需要以某种方式计算它。最理想的情况是,我想知道这两个多边形创建的区域有多少是常见的。例如,根据需要查看图片。

一个有效(但缓慢)的解决方案:
- 在空图像上绘制一个多边形 (cv::fillConvexPoly)
- 在空图像上绘制多边形二 (cv::fillConvexPoly)
- 执行按位并创建所有重叠像素的图像
- 计算所有非零像素 --> 重叠像素
- 反转第一张图像并用非反转的第二张重复 --> 过多的像素
- 反转第二张图像并使用未反转的第一张图像重复 --> 更多像素
- 将“重叠像素”与“过多像素”的总和相比较

如您所见,当前的解决方案是计算密集型的,因为它要对图像的每个像素进行大约 12 次评估/操作。我宁愿一个解决方案,计算这个区域,通过繁琐的构建和评估几个图像。

现有代码:

#define POLYGONSCALING 0.05
typedef std::array<cv::Point, 4> Polygon;

float PercentMatch( const Polygon& polygon,
                    const cv::Mat optimalmat )
{
    //Create blank mats
    cv::Mat polygonmat{ cv::Mat(optimalmat.rows, optimalmat.cols, CV_8UC1, cv::Scalar(0)) };
    cv::Mat resultmat{ cv::Mat(optimalmat.rows, optimalmat.cols, CV_8UC1, cv::Scalar(0)) };

    //Draw polygon
    cv::Point cvpointarray[4];
    for  (int i =0; i < 4; i++ ) {
        cvpointarray[i] = cv::Point(POLYGONSCALING * polygon[i].x, POLYGONSCALING *
            polygon[i].y);
    }
    cv::fillConvexPoly( polygonmat, cvpointarray, 4,  cv::Scalar(255) );

    //Find overlapped pixels
    cv::bitwise_and(polygonmat, optimalmat, resultmat);
    int overlappedpixels { countNonZero(resultmat) };

    //Find excessive pixels
    cv::bitwise_not(optimalmat, resultmat);
    cv::bitwise_and(polygonmat, resultmat, resultmat);
    int excessivepixels { countNonZero(resultmat) };
    cv::bitwise_not(polygonmat, resultmat);
    cv::bitwise_and(optimalmat, resultmat, resultmat);
    excessivepixels += countNonZero(resultmat);

    return (100.0 * overlappedpixels) / (overlappedpixels + excessivepixels);
}

目前我设计的唯一性能改进是在函数外部绘制“最佳垫”,因此不会重绘(它与许多其他多边形相比),而且我添加了一个 POLYGONSCALING 来缩小多边形的大小和失去一些分辨率但获得一些性能。还是太慢了。

【问题讨论】:

  • Monte Carlo 也许?
  • 或者你可以试着想出一个公式来计算重叠面积。您拥有所需的所有信息:每个梯形的点。
  • 我会想出类似于 Mark Setchell 的答案。如果您可以在精度和处理速度之间进行权衡,您可以缩放梯形和图像以提高速度和/或如果梯形与图像相比较小,则使用边界框和子图像。根本没有讨论精度本身。也许 2D CSG 方法也可以,但我没有任何经验......
  • 仔细看,重叠区域也是一个梯形。如果你知道每个梯形的点,那么你可以很容易地分解成重叠区域的面积方程。这可以与每个梯形的面积进行比较,以给出重叠百分比。您可以尝试的另一件事是减小图像的大小

标签: c++ opencv image-processing


【解决方案1】:

我可能误解了你想要什么,但我认为你应该能够像这样更快地做到这一点......

  • 在零背景上用 1 填充您的第一个梯形。
  • 在零背景上用 2 填充第二个梯形。
  • 将两个垫子加在一起。

现在每个像素必须是 0、1、2 或 3。创建一个包含 4 个元素的数组,并一次性遍历所有元素,没有if 语句,只需根据值递增相应的数组元素每个像素。

那么数组的第一个索引中的总像素不是梯形存在的地方,索引为 1 和 2 的元素是梯形 1 或 2 存在的地方,索引为 3 的元素是重叠的。

另外,请尝试对两个梯形的填充填充进行基准测试,如果您的时间占很大比例,则可能有第二个线程填充第二个梯形。

基准测试

我编写了一些代码来尝试上述理论,并使用 640x480 的图像:

  • 绘制第一个多边形需要 181 微秒
  • 84 微秒绘制第二个多边形
  • 481 微秒计算重叠

所以我的 iMac 上的总时间是 740 微秒。

您可以与第一个平行地绘制第二个多边形,但线程创建和加入时间大约为 20 微秒,因此您只会节省 60 微秒,即 8% 左右 - 可能不值得。

大部分代码是定时和调试:

#include "opencv2/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <chrono>

using namespace cv;
using namespace std;

const int COLS=640;
const int ROWS=480;

typedef std::chrono::high_resolution_clock hrclock;
hrclock::time_point t1,t2;
std::chrono::nanoseconds elapsed;
int e;

int
main(int argc,char*argv[]){

   Mat canvas1(ROWS,COLS,CV_8UC1,Scalar(0));
   Mat canvas2(ROWS,COLS,CV_8UC1,Scalar(0));
   Mat sum(ROWS,COLS,CV_8UC1,Scalar(0));

   //Draw polygons on canvases
   Point vertices1[4]={Point(10,10),Point(400,10),Point(400,460),Point(10,460)};
   Point vertices2[4]={Point(300,50),Point(600,50),Point(600,400),Point(300,400)};

   t1 = hrclock::now();
   // FilConvexPoly takes around 150 microseconds here
   fillConvexPoly(canvas1,vertices1,4,cv::Scalar(1));
   t2 = hrclock::now();
   elapsed = t2-t1;
   e=elapsed.count();
   cout << "fillConvexPoly: " << e << "ns" << std::endl;

   imwrite("canvas1.png",canvas1);

   t1 = hrclock::now();
   // FilConvexPoly takes around 80 microseconds here
   fillConvexPoly(canvas2,vertices2,4,cv::Scalar(2));
   t2 = hrclock::now();
   elapsed = t2-t1;
   e=elapsed.count();
   cout << "fillConvexPoly: " << e << "ns" << std::endl;
   imwrite("canvas2.png",canvas2);
   sum=canvas1+canvas2;
   imwrite("sum.png",sum);

   long totals[4]={0,0,0,0};
   uchar* p1=sum.data;
   t1 = hrclock::now();
   for(int j=0;j<ROWS;j++){
      uchar* data= sum.ptr<uchar>(j);
      for(int i=0;i<COLS;i++) {
         totals[data[i]]++;
      }
   }
   t2 = hrclock::now();
   elapsed = t2-t1;
   e=elapsed.count();
   cout << "Count overlap: " << e << "ns" << std::endl;
   for(int i=0;i<4;i++){
      cout << "totals[" << i << "]=" << totals[i] << std::endl;
   }
}

示例运行

fillConvexPoly: 181338ns
fillConvexPoly: 84759ns
Count overlap: 481830ns
totals[0]=60659
totals[1]=140890
totals[2]=70200
totals[3]=35451

使用 ImageMagick 验证如下:

identify -verbose sum.png | grep -A4 Histogram:

Histogram:
 60659: (  0,  0,  0) #000000 gray(0)
140890: (  1,  1,  1) #010101 gray(1)
 70200: (  2,  2,  2) #020202 gray(2)
 35451: (  3,  3,  3) #030303 gray(3)

【讨论】:

  • 两个很棒的建议,马克!如果这不是我的确切解决方案,那么利用像素的非布尔性质是节省步骤的好主意。我会尝试并测试它节省了多少时间,然后可能多线程可以真正优化它。谢谢
  • 哇,更好。实际上,我昨晚得到了一些运行良好的代码,只是我还没有进行基准测试。很好的答案,感谢您的帮助!
猜你喜欢
  • 2012-04-05
  • 1970-01-01
  • 2010-11-29
  • 2020-10-09
  • 2019-08-18
  • 2021-01-25
  • 1970-01-01
  • 1970-01-01
  • 2012-02-10
相关资源
最近更新 更多