现在市面上很多人体追踪产品都有实时展示人体骨骼线的需求,绝大部分的骨骼线和骨骼点都是使用 line() 和 circle() 来解决的。这种方式简单明了,只需要知道两个骨骼点的位置就可以实现。实现的效果基本和下图类似:
但是这种单一的线条缺乏视觉上的冲突感。所以希望利用同样的骨骼点信息画出更有“感觉”的骨格线。这里介绍一种实现方式,希望能给到大家一些启发。
首先确定骨格线需要是什么形状的。这里介绍一种菱形骨格线。
按照原本的画线,最直观的信息就是两个骨骼点的坐标,通过坐标就可以计算向量以指定骨格线的方向,还可以计算骨骼点之间的距离,利用这两个点的坐标可以计算出两点连线上任意点的坐标。这里定义连线上距离起点 1/8 处的坐标为中间点并计算骨骼点连线的法向量,可以获得经过中间点并且与骨骼点连线垂直的直线上任意点的坐标。这样菱形的4个顶点坐标就都能计算出来了。
#pragma once
#include <memory>
#include <string>
struct else_two_point
{
double left_point_x;
double left_point_y;
double right_point_x;
double right_point_y;
};
//已知两点计算菱形另外两点坐标,你们也可以不用这样返回值,这样是为了方便直接调用。
else_two_point calculate_else_two_point(double point1_x, double point1_y, double point2_x, double point2_y) {
//声明中间点
k4a_float2_t center_point;
//骨骼点之间的距离
double distance4p1p2 = sqrt(pow(point1_x - point2_x, 2) + pow(point1_y - point2_y, 2));
//以起点到终点1/8处坐标定义中间点
center_point.xy.x = point1_x - (point1_x - point2_x) / 8;
center_point.xy.y = point1_y - (point1_y - point2_y) / 8;
//声明另外两点
else_two_point else_two;
//通过法向量和一定距离定义另外两点
else_two.left_point_x = center_point.xy.x + ((distance4p1p2 / 8) * ((point2_y - point1_y) / distance4p1p2));
else_two.left_point_y = center_point.xy.y - ((distance4p1p2 / 8) * ((point2_x - point1_x) / distance4p1p2));
else_two.right_point_x = center_point.xy.x - ((distance4p1p2 / 8) * ((point2_y - point1_y) / distance4p1p2));
else_two.right_point_y = center_point.xy.y + ((distance4p1p2 / 8) * ((point2_x - point1_x) / distance4p1p2));
//返回另外两点
return else_two;
}
利用比例关系还能让这种菱形在固定形状的同时可以随骨骼点距离和位置改变大小和方向。
上面知道了如何获取菱形的其他两个顶点,下面介绍如何画出图形。主要是利用openCV中的fillPoly()函数。
//所处位置 imgproc.hpp
//第一个参数是画布
//第二个参数是每个多边形的顶点集(可以通过2维数组定义不同的多边形的顶点)
//第三个参数是要绘制多边形的顶点个数
//第四个参数是要绘制图形的数量
//第五个参数是填充颜色
//第六个参数是线型
CV_EXPORTS void fillPoly(Mat& img, const Point** pts,
const int* npts, int ncontours,
const Scalar& color, int lineType = LINE_8, int shift = 0,
Point offset = Point() );
先看代码比较枯燥,所以先看看效果吧。
然后上完整代码,下面代码能画出一条骨骼线,在需要画线的骨骼点之间调用 draw_bone() 即可达到上图效果。
//绘制菱形
void drawPoly(Mat& src, Point** pts, int npts)
{
const Point* ppt[1] = { pts[0] };
int npt[] = { npts };
int lineType = LINE_AA;
fillPoly(src, ppt, npt, 1, Scalar(255, 255, 85), lineType);
}
//已知两点计算菱形另外两点坐标
else_two_point calculate_else_two_point(double point1_x, double point1_y, double point2_x, double point2_y) {
k4a_float2_t center_point;
double distance4p1p2 = sqrt(pow(point1_x - point2_x, 2) + pow(point1_y - point2_y, 2));
center_point.xy.x = point1_x - (point1_x - point2_x) / 8;
center_point.xy.y = point1_y - (point1_y - point2_y) / 8;
else_two_point else_two;
else_two.left_point_x = center_point.xy.x + ((distance4p1p2 / 8) * ((point2_y - point1_y) / distance4p1p2));
else_two.left_point_y = center_point.xy.y - ((distance4p1p2 / 8) * ((point2_x - point1_x) / distance4p1p2));
else_two.right_point_x = center_point.xy.x - ((distance4p1p2 / 8) * ((point2_y - point1_y) / distance4p1p2));
else_two.right_point_y = center_point.xy.y + ((distance4p1p2 / 8) * ((point2_x - point1_x) / distance4p1p2));
return else_two;
}
//绘制自制两个骨骼点之间菱形线
void draw_bone(Mat& src, double point1_x, double point1_y, double point2_x, double point2_y) {
// 定义多边形的顶点
Point** rook_points = new Point * [1];
rook_points[0] = new Point[4];
rook_points[0][0] = Point(point1_x, point1_y);
rook_points[0][2] = Point(point2_x, point2_y);
else_two_point else_two = calculate_else_two_point(point1_x, point1_y, point2_x, point2_y);
rook_points[0][1] = Point(else_two.left_point_x, else_two.left_point_y);
rook_points[0][3] = Point(else_two.right_point_x, else_two.right_point_y);
// 绘制菱形
drawPoly(src, rook_points, 4);
imshow("菱形骨格线", src);
}
大家可以开放脑洞,尝试绘制其他图形。