背景:
项目中有个需求点,涉及到两个模块,A、B
A:
在图像区域绘制一个ROI,可能是任意形状的,然后A会将轮廓的一些点传给模块B,让B去计算参数
B:
获取该ROI内的所有点数据
现在想到的有两种做法可以实现该模块
两种做法的共有部分:
由于A提供的点有可能是不连续的,所以可能需要进行一个插值,当然这个可以做可以不做,看具体误差要求
插值基本逻辑
struct CvPointCompare
{
template<typename T> bool operator()(const T &p1, const T &p2)
{
return (fabs(static_cast<float>(p1.x - p2.x)) < 0.0001f) ? (p1.y < p2.y) : (p1.x < p2.x);
}
};
// 输入点集合,得到闭合线或者非闭合线
void GetLineByPoints(const std::vector<CvPoint> &Pt, bool bIsCircle, std::set<cv::Point2f, CvPointCompare> &sLine)
{
sLine.clear(); // 清除集合中的元素
int iSum = Pt.size();
if (iSum > 0)
{
if (bIsCircle) // 环形的时候
{
for (int i = 0; i < iSum; i++)
{
((iSum - 1) == i) ? GetLineByTwoPoints(Pt[iSum - 1], Pt[0], sLine) : GetLineByTwoPoints(Pt[i], Pt[i + 1], sLine);
}
}
else // 非环形的时候
{
for (int i = 0; i < iSum - 1; i++)
{
GetLineByTwoPoints(Pt[i], Pt[i + 1], sLine);
}
}
sLine.insert(cvPointTo32f(Pt.back())); // 插入最后一个点(防止遗漏,非闭合跟只有一个点的时候会出现遗漏)
}
}
void GetTraceSelectedPoints(std::vector<CvPoint> &vBoundaryArea, std::vector<CvPoint> &vArea)
{
// 需要自定义排序
std::set<cv::Point2f, CvPointCompare> sOutLine; // 轮廓点集合
GetLineByPoints(vBoundaryArea, true, sOutLine); // 获取插值后的轮阔点集合
}
然后进行后续处理
方法1:测试每个点是否在区域内部
基本逻辑:
void GetTraceSelectedPoints(std::vector<CvPoint> &vBoundaryArea, std::vector<CvPoint> &vArea)
{
// 需要自定义排序
std::set<cv::Point2f, CvPointCompare> sOutLine; // 轮廓点集合
GetLineByPoints(vBoundaryArea, true, sOutLine); // 获取插值后的轮阔点集合
// 计算轮廓内所有点
std::vector<cv::Point2f> vTmp(sOutLine.begin(), sOutLine.end()); // 转存到容器(set已经实现了排序过程)
float fMinY = vTmp.front().y;
float fMaxY = vTmp.front().y;
for (cv::Point2f &pt : vTmp)
{
if (pt.y > fMaxY)
{
fMaxY = pt.y;
}
else if (pt.y < fMinY)
{
fMinY = pt.y;
}
}
CvPoint LeftTop = cvPointFrom32f(cvPoint2D32f(vTmp.front().x, fMinY));
CvPoint RightDown = cvPointFrom32f(cvPoint2D32f(vTmp.back().x, fMaxY));
vArea.clear();
for (int y = LeftTop.y; y <= RightDown.y; y++)
{
for (int x = LeftTop.x; x <= RightDown.x; x++)
{
cv::Point2f PCur = cvPoint2D32f(x, y);
if (!(pointPolygonTest(vTmp, PCur, false) < 0)) // 判断当前点是否在轮廓内
{
vArea.push_back(cvPointFrom32f(PCur));
}
}
}
}
方法2:直接得到回轮廓内部点
int main()
{
std::vector<uint8_t> v_data(500 * 500, 0);
cv::Mat cv_back_end = cv::Mat(500, 500, CV_8UC1, v_data.data());
// 这个位置就是模拟前面插值后的轮廓点
std::vector<std::vector<cv::Point>> v_points = { {cv::Point(98, 2), cv::Point(40, 40), cv::Point(2, 98), cv::Point(98, 98) } };
//直接将轮廓内的所有点绘制在back_end上,这里的关键点就是thickness参数(最后一个参数)设置成了-1,设置成-1,opencv就会将轮廓内填充起来
cv::drawContours(cv_back_end, v_points, -1, cv::Scalar(255), -1);
}