实战:基于OpenPose的卡通人物可视化 | CSDN博文精选

作者 | Wuzebiao2016
来源 | CSDN博客
前言
去年打算用些现成的Pose做些展示,因为以前有在OpenPose做些识别等开发工作,所以这次我就简单在OpenPose上把骨架用动画填充上去,关于能够和人动作联系起来的动画,我找到了Unity提供的示例Anima2D,但是Unity学得不是很熟,我就用PhotoShop把Anima2D的每个部位截取保存下来,然后使用OpenCV把各个部位读取进去,做些数学计算,把各个部位贴到OpenPose得出的骨架结果上,最后得到一个简单的2D动作展示,代码我会贴出来,下面写下我的思路,方法很简单。

思路描述
找个动画,把它的关节拆开,我选用了Anima2D的一个example。


用Photoshop把各个部位拆开,这样子方法用OpenCV读取各个关节来做旋转变化。

3. 根据OpenPose的18个关节点的位置,制定切实可行的关节旋转计划,因为旋转需要其余点绕一个定点来旋转,而一个关节的旋转势必会带动与它相关连的所有点的旋转,所以这一部分要考虑清楚。下面是我当时做的各类型的草稿,大致内容是在研究怎么旋转,这里没有详细体现出来。

4. 然后我设计了一个很大致的关节点联系的草图,大家看具体的关节点联系就好了,我当时是给每个关节标号了。

就是由上面这个玩意,我最后将各个部位一步一步组成一个完整形态的机器人,可能和官方的example有一点差距,但是过程中有些麻烦的东西我就给它去掉了。
6. 代码细节我就不细讲了,就是些简单的数学旋转变化,再调一调就出来了,另外这个可以加入SVM,将关节点回归某些指定的动作,这样可以实现在动画过程中你做出指定的动作后动画界面做出一些反馈,最简单的就是你比划两下然后头部的颜色就从白色变成黑色,或把它背景颜色改一下,实现一些用户交互性的东西这些自己去研究,SVM这个简单可行,刚好加到VS里面编译一下就出来了,下面是代码,我就加了一些自己的东西。
图片资源在CSDN下载那里搜索“Anima2D的Robot图片 ”就有了。
namespace gflags = google;
void saveBead(cv::Mat *background, cv::Mat *bead, int X_, int Y_, int tarX, int tarY);
int cvAdd4cMat_q(cv::Mat &dst, cv::Mat &scr, double scale);
cv::Mat rotate(cv::Mat src, double angle);
void getPoint(int *point, int srcX, int srcY, double angle, int srcImgCols, int srcImgRows, int tarImgCols, int tarImgRows);
cv::Mat getRobot(op::Array<float> poseKeypoints,double scale);
double getAngle(double p0_x, double p0_y, double p1_x, double p1_y);
void resizeImg(double scale);
// Robot Param
int point[2] = { 0,0 };
double scale = 1;
cv::Mat
img0_s = cv::imread("D:/Robot/background0.png"),
img1_s = cv::imread("D:/Robot/1.png", -1),
img2_s = cv::imread("D:/Robot/2.png", -1),
head_s = cv::imread("D:/Robot/3.png", -1),
img4_s = cv::imread("D:/Robot/4.png", -1),
foot_s = cv::imread("D:/Robot/5.png", -1),
leg_s = cv::imread("D:/Robot/6.png", -1),
img7_s = cv::imread("D:/Robot/7.png", -1),
bead_s = cv::imread("D:/Robot/8.png", -1),
m1_s = cv::imread("D:/Robot/9.png", -1),
m2_s = cv::imread("D:/Robot/10.png", -1),
hand_s = cv::imread("D:/Robot/11.png", -1),
neck_s = cv::imread("D:/Robot/12.png", -1),
body_s = cv::imread("D:/Robot/13.png", -1),
circle_s = cv::imread("D:/Robot/14.png", -1),
img15_s = cv::imread("D:/Robot/15.png", -1),
handcenter_s = cv::imread("D:/Robot/16.png", -1), background,
b1= cv::imread("D:/Robot/background1.png")
;
cv::Mat img0 = img0_s.clone(),
img1 = img1_s.clone(),
img2 = img2_s.clone(),
head = head_s.clone(),
img4 = img4_s.clone(),
foot = foot_s.clone(),
leg = leg_s.clone(),
img7 = img7_s.clone(),
bead = bead_s.clone(),
m1 = m1_s.clone(),
m2 = m2_s.clone(),
hand = hand_s.clone(),
neck = neck_s.clone(),
body = body_s.clone(),
circle = circle_s.clone(),
img15 = img15_s.clone(),
handcenter = handcenter_s.clone();
int camera_rows = 720, camera_cols = 1280;
//int ccount = 0;
struct UserDatum : public op::Datum
{
bool boolThatUserNeedsForSomeReason;
UserDatum(const bool boolThatUserNeedsForSomeReason_ = false) :
boolThatUserNeedsForSomeReason{ boolThatUserNeedsForSomeReason_ }
{}
};
class WUserOutput : public op::WorkerConsumer<std::shared_ptr<std::vector<UserDatum>>>
{
public:
void initializationOnThread() {}
void workConsumer(const std::shared_ptr<std::vector<UserDatum>>& datumsPtr)
{
try
{
if (datumsPtr != nullptr && !datumsPtr->empty())
{
const auto& poseKeypoints = datumsPtr->at(0).poseKeypoints;
const auto& poseHeatMaps = datumsPtr->at(0).poseHeatMaps
background = img0.clone();
cv::Mat d2 , d1 = datumsPtr->at(0).cvOutputData,temp, d0 = datumsPtr->at(0).cvInputData;
cv::resize(background, temp, cv::Size(640, 360), (0, 0), (0, 0), cv::INTER_LINEAR);
if (poseKeypoints.empty())
{
cv::imshow("卡通界面", temp);
}
else
{
d2 = getRobot(poseKeypoints, scale);
cv::resize(d2, d2, cv::Size(640, 360), (0, 0), (0, 0), cv::INTER_LINEAR);
cv::imshow("卡通界面", d2);
}
cv::resize(d0, d0, cv::Size(640, 360), (0, 0), (0, 0), cv::INTER_LINEAR);
cv::imshow("摄像头界面", d0);
const char key = (char)cv::waitKey(1);
if (key == 27)
this->stop();
}
}
catch (const std::exception& e)
{
this->stop();
op::error(e.what(), __LINE__, __FUNCTION__, __FILE__);
}
}
};
int openPoseDemo()
{
try
{
op::log("Starting OpenPose demo...", op::Priority::High);
const auto timerBegin = std::chrono::high_resolution_clock::now();
op::check(0 <= 3 && 3 <= 255, "Wrong logging_level value.",
__LINE__, __FUNCTION__, __FILE__);
op::ConfigureLog::setPriorityThreshold((op::Priority)3);
op::Profiler::setDefaultX(1000);
const auto outputSize = op::flagsToPoint("-1x-1", "-1x-1");
// netInputSize
const auto netInputSize = op::flagsToPoint("-1x256", "-1x368");
// faceNetInputSize
const auto faceNetInputSize = op::flagsToPoint("368x368", "368x368 (multiples of 16)");
// handNetInputSize
const auto handNetInputSize = op::flagsToPoint("368x368", "368x368 (multiples of 16)");
// producerType
const auto producerSharedPtr = op::flagsToProducer("", "", "", -1,
false, "-1x-1", 30,
"models/cameraParameters/flir/", !false,
(unsigned int)1, -1);
// poseModel
const auto poseModel = op::flagsToPoseModel("COCO");
// JSON saving
//if (!FLAGS_write_keypoint.empty())
// op::log("Flag `write_keypoint` is deprecated and will eventually be removed."
// " Please, use `write_json` instead.", op::Priority::Max);
// keypointScale
const auto keypointScale = op::flagsToScaleMode(0);
// heatmaps to add
const auto heatMapTypes = op::flagsToHeatMaps(false, false,
false);
const auto heatMapScale = op::flagsToHeatMapScaleMode(2);
// >1 camera view?
const auto multipleView = (false || 1 > 1 || false);
// Enabling Google Logging
const bool enableGoogleLogging = true;
op::Wrapper<std::vector<UserDatum>> opWrapper;
auto wUserOutput = std::make_shared<WUserOutput>();
// Add custom processing
const auto workerOutputOnNewThread = true;
opWrapper.setWorkerOutput(wUserOutput, workerOutputOnNewThread);
// Pose configuration (use WrapperStructPose{} for default and recommended configuration)
const op::WrapperStructPose wrapperStructPose{
!false, netInputSize, outputSize, keypointScale, -1, 0,
1, (float)0.3, op::flagsToRenderMode(-1, multipleView),
poseModel, !false, (float)0.6, (float)0.7,
0, "models/", heatMapTypes, heatMapScale, false,
(float)0.05, -1, enableGoogleLogging };
// Face configuration (use op::WrapperStructFace{} to disable it)
const op::WrapperStructFace wrapperStructFace{
false, faceNetInputSize, op::flagsToRenderMode(-1, multipleView, -1),
(float)0.6, (float)0.7, (float)0.2 };
// Hand configuration (use op::WrapperStructHand{} to disable it)
const op::WrapperStructHand wrapperStructHand{
false, handNetInputSize, 1, (float)0.4, false,
op::flagsToRenderMode(-1, multipleView, -1), (float)0.6,
(float)0.7, (float)0.2 };
// Extra functionality configuration (use op::WrapperStructExtra{} to disable it)
const op::WrapperStructExtra wrapperStructExtra{
false, -1, false, -1, 0 };
// Producer (use default to disable any input)
const op::WrapperStructInput wrapperStructInput{producerSharedPtr, 0, (unsigned int)-1, false, false, 0, false };
const auto displayMode = op::DisplayMode::NoDisplay;
const bool guiVerbose = false;
const bool fullScreen = false;
const op::WrapperStructOutput wrapperStructOutput{
displayMode, guiVerbose, fullScreen, "",
op::stringToDataFormat("yml"), "", "",
"", "", "png", "",
30, "", "png", "",
"", "", "8051" };
// Configure wrapper
opWrapper.configure(wrapperStructPose, wrapperStructFace, wrapperStructHand, wrapperStructExtra,
wrapperStructInput, wrapperStructOutput);
// Set to single-thread running (to debug and/or reduce latency)
if (false)
opWrapper.disableMultiThreading();
op::log("Starting thread(s)...", op::Priority::High);
opWrapper.exec();
const auto now = std::chrono::high_resolution_clock::now();
const auto totalTimeSec = (double)std::chrono::duration_cast<std::chrono::nanoseconds>(now - timerBegin).count()
* 1e-9;
const auto message = "OpenPose demo successfully finished. Total time: "
+ std::to_string(totalTimeSec) + " seconds.";
op::log(message, op::Priority::High);
return 0;
}
catch (const std::exception& e)
{
op::error(e.what(), __LINE__, __FUNCTION__, __FILE__);
return -1;
}
}
int main(int argc, char *argv[])
{
resizeImg(0.7);
gflags::ParseCommandLineFlags(&argc, &argv, true);
return openPoseDemo();
}
cv::Mat getRobot(op::Array<float> poseKeypoints,double scale)
{
//我根据传入的关键点序列构造了一个完整的机器人的形状,由最初的背景,一点一点地把部件贴上去,最麻烦的就是计算部件旋转带动其他部件的关系
//我在这里用的方法是指定每个部件在前一个部件的相对位置
//后面使用了指针,所以这里还是需要克隆一下,不然每次换帧都在同一张背景下会导致有很多叠影
for (int person = 0; person < poseKeypoints.getSize(0); person++)
{
//std::cout << sqrt((poseKeypoints[{person, 1, 0}] - poseKeypoints[{person, 0, 0}])*(poseKeypoints[{person, 1, 0}] - poseKeypoints[{person, 0, 0}]) + (poseKeypoints[{person, 1, 1}] - poseKeypoints[{person, 0, 1}])*(poseKeypoints[{person, 1, 1}] - poseKeypoints[{person, 0, 1}])) << std::endl;
int center_x = background.cols *poseKeypoints[{person, 1, 0}] * 1.0 / camera_cols;
int center_y = background.rows *poseKeypoints[{person, 1, 1}] * 1.0 / camera_rows;
double p0_x, p0_y, p1_x, p1_y, x, y, a;
double bodyAngle = getAngle(poseKeypoints[{person, 1, 0}], poseKeypoints[{person, 1, 1}], (poseKeypoints[{person, 8, 0}] + poseKeypoints[{person, 11, 0}]) / 2.0, (poseKeypoints[{person, 8, 1}] + poseKeypoints[{person, 11, 1}]) / 2.0);
if (poseKeypoints[{person, 1, 0}] == 0 || poseKeypoints[{person, 1, 1}] == 0 || poseKeypoints[{person, 8, 0}] == 0 || poseKeypoints[{person, 11, 0}] == 0 || poseKeypoints[{person, 8, 1}] == 0 || poseKeypoints[{person, 11, 0}] == 0)
bodyAngle = 0;
//根据OpenPose的关键点分布,我由关键点1,关键点8和11计算出了身体部位的偏转角
cv::Mat neck_ = rotate(neck, bodyAngle);//脖子的偏转随身体
getPoint(point, neck.cols / 2, neck.rows / 2, bodyAngle, neck.cols, neck.rows, neck_.cols, neck_.rows);//获得翻转部位指定关键点
saveBead(&background, &neck_, center_x, center_y, point[0], point[1]);//将翻转后的部位还原到背景图像中
cv::Mat body_ = rotate(body, bodyAngle + 7);//取得部位以及翻转图像
int body_0_X = body.cols / 2, body_0_Y = 25 * scale, //根据原图我确定了五个关键位置,脖子连接部位以及四肢连接的部位
body_1_X = 15 * scale, body_1_Y = body.rows / 5,
body_2_X = body.cols - 10 * scale, body_2_Y = body.rows / 5,
body_3_X = body.cols / 4, body_3_Y = body.rows * 4 / 5,
body_4_X = body.cols * 3 / 4, body_4_Y = body.rows * 4 / 5;
//获得身体旋转之后的五个关键点的坐标
getPoint(point, body_0_X, body_0_Y, bodyAngle, body.cols, body.rows, body_.cols, body_.rows);
body_0_X = point[0]; body_0_Y = point[1];
getPoint(point, body_1_X, body_1_Y, bodyAngle, body.cols, body.rows, body_.cols, body_.rows);
body_1_X = point[0]; body_1_Y = point[1];
getPoint(point, body_2_X, body_2_Y, bodyAngle, body.cols, body.rows, body_.cols, body_.rows);
body_2_X = point[0]; body_2_Y = point[1];
getPoint(point, body_3_X, body_3_Y, bodyAngle, body.cols, body.rows, body_.cols, body_.rows);
body_3_X = point[0]; body_3_Y = point[1];
getPoint(point, body_4_X, body_4_Y, bodyAngle, body.cols, body.rows, body_.cols, body_.rows);
body_4_X = point[0]; body_4_Y = point[1];
//保存下肢的两个绿色的摩擦块,因为身体要覆盖摩擦块,所以我先保存两个摩擦块,在保存身体
cv::Mat m1_ = rotate(m1, bodyAngle);
saveBead(&background, &m1_, body_3_X - body_0_X + center_x, 15 * scale + body_3_Y - body_0_Y + center_y, m1_.cols / 2, m1_.rows / 2);
saveBead(&background, &m1_, body_4_X - body_0_X + center_x, 15 * scale + body_4_Y - body_0_Y + center_y, m1_.cols / 2, m1_.rows / 2);
saveBead(&background, &body_, center_x, center_y, body_0_X, body_0_Y);//将翻转后的部位还原到背景图像中
//左臂
double m_1x, m_1y, m_0x, m_0y, m_2x, m_2y, m_3x, m_3y;
//手臂的上半部分计算
double hand1Angle = getAngle(poseKeypoints[{person, 2, 0}], poseKeypoints[{person, 2, 1}], poseKeypoints[{person, 3, 0}], poseKeypoints[{person, 3, 1}]);
cv::Mat hand1 = rotate(hand, hand1Angle), m2_ = rotate(m2, hand1Angle);
getPoint(point, hand.cols / 2, 0, hand1Angle, hand.cols, hand.rows, hand1.cols, hand1.rows);//获得翻转部位指定关键点
m_0x = point[0]; m_0y = point[1];
getPoint(point, hand.cols / 2, hand.rows, hand1Angle, hand.cols, hand.rows, hand1.cols, hand1.rows);//获得翻转部位指定关键点
m_1x = point[0]; m_1y = point[1];
//手臂的下半部分计算
double hand2Angle = getAngle(poseKeypoints[{person, 3, 0}], poseKeypoints[{person, 3, 1}], poseKeypoints[{person, 4, 0}], poseKeypoints[{person, 4, 1}]);
cv::Mat hand2 = rotate(hand, hand2Angle), hand2center = rotate(handcenter, hand2Angle + 180);
getPoint(point, hand.cols / 2, 0, hand2Angle, hand.cols, hand.rows, hand2.cols, hand2.rows);//获得翻转部位指定关键点
m_2x = point[0]; m_2y = point[1];
getPoint(point, hand.cols / 2, hand.rows, hand2Angle, hand.cols, hand.rows, hand2.cols, hand2.rows);//获得翻转部位指定关键点
m_3x = point[0]; m_3y = point[1];
//保存整支手臂
saveBead(&background, &hand1, body_1_X - body_0_X + center_x, body_1_Y - body_0_Y + center_y, m_0x, m_0y);
saveBead(&background, &hand2, m_3x - m_2x + m_1x - m_0x + body_1_X - body_0_X + center_x, m_3y - m_2y + m_1y - m_0y + body_1_Y - body_0_Y + center_y, m_3x, m_3y);
saveBead(&background, &circle, m_1x - m_0x + body_1_X - body_0_X + center_x, m_1y - m_0y + body_1_Y - body_0_Y + center_y, circle.cols / 2, circle.rows / 2);
saveBead(&background, &hand2center, m_3x - m_2x + m_1x - m_0x + body_1_X - body_0_X + center_x, m_3y - m_2y + m_1y - m_0y + body_1_Y - body_0_Y + center_y, hand2center.cols / 2, hand2center.rows / 2);
saveBead(&background, &bead, body_1_X - body_0_X + center_x, body_1_Y - body_0_Y + center_y, bead.cols / 2, bead.rows / 2);
//头部
double headAngle = getAngle(poseKeypoints[{person, 0, 0}], poseKeypoints[{person, 0, 1}], poseKeypoints[{person, 1, 0}], poseKeypoints[{person, 1, 1}]);
cv::Mat head_ = rotate(head, headAngle);
getPoint(point, head.cols / 2 - 30 * scale, head.rows, headAngle, head.cols, head.rows, head_.cols, head_.rows);//获得翻转部位指定关键点
saveBead(&background, &head_, center_x, center_y, point[0], point[1]);//将翻转后的部位还原到背景图像中
//右臂
//手臂的上半部分计算
double hand3Angle = getAngle(poseKeypoints[{person, 5, 0}], poseKeypoints[{person, 5, 1}], poseKeypoints[{person, 6, 0}], poseKeypoints[{person, 6, 1}]);
cv::Mat hand3 = rotate(hand, hand3Angle);
getPoint(point, hand.cols / 2, 0, hand3Angle, hand.cols, hand.rows, hand3.cols, hand3.rows);//获得翻转部位指定关键点
m_0x = point[0]; m_0y = point[1];
getPoint(point, hand.cols / 2, hand.rows, hand3Angle, hand.cols, hand.rows, hand3.cols, hand3.rows);//获得翻转部位指定关键点
m_1x = point[0]; m_1y = point[1];
//手臂的下半部分计算
double hand4Angle = getAngle(poseKeypoints[{person, 6, 0}], poseKeypoints[{person, 6, 1}], poseKeypoints[{person, 7, 0}], poseKeypoints[{person, 7, 1}]);
cv::Mat hand4 = rotate(hand, hand4Angle), hand4center = rotate(handcenter, hand4Angle);
getPoint(point, hand.cols / 2, 0, hand4Angle, hand.cols, hand.rows, hand4.cols, hand4.rows);//获得翻转部位指定关键点
m_2x = point[0]; m_2y = point[1];
getPoint(point, hand.cols / 2, hand.rows, hand4Angle, hand.cols, hand.rows, hand4.cols, hand4.rows);//获得翻转部位指定关键点
m_3x = point[0]; m_3y = point[1];
//保存整支手臂
saveBead(&background, &hand3, body_2_X - body_0_X + center_x, body_2_Y - body_0_Y + center_y, m_0x, m_0y);
saveBead(&background, &hand4, m_3x - m_2x + m_1x - m_0x + body_2_X - body_0_X + center_x, m_3y - m_2y + m_1y - m_0y + body_2_Y - body_0_Y + center_y, m_3x, m_3y);
saveBead(&background, &circle, m_1x - m_0x + body_2_X - body_0_X + center_x, m_1y - m_0y + body_2_Y - body_0_Y + center_y, circle.cols / 2, circle.rows / 2);
saveBead(&background, &hand4center, m_3x - m_2x + m_1x - m_0x + body_2_X - body_0_X + center_x, m_3y - m_2y + m_1y - m_0y + body_2_Y - body_0_Y + center_y, hand4center.cols / 2, hand4center.rows / 2);
saveBead(&background, &bead, body_2_X - body_0_X + center_x, body_2_Y - body_0_Y + center_y, bead.cols / 2, bead.rows / 2);
//左腿
//左腿的上半部分计算
double leg1Angle = getAngle(poseKeypoints[{person, 8, 0}], poseKeypoints[{person, 8, 1}], poseKeypoints[{person, 9, 0}], poseKeypoints[{person, 9, 1}]);
cv::Mat leg1 = rotate(leg, leg1Angle);
getPoint(point, leg.cols / 2, 0, leg1Angle, leg.cols, leg.rows, leg1.cols, leg1.rows);//获得翻转部位指定关键点
m_0x = point[0]; m_0y = point[1];
getPoint(point, leg.cols / 2, leg.rows, leg1Angle, leg.cols, leg.rows, leg1.cols, leg1.rows);//获得翻转部位指定关键点
m_1x = point[0]; m_1y = point[1];
//左腿的下半部分计算
double leg2Angle = getAngle(poseKeypoints[{person, 9, 0}], poseKeypoints[{person, 9, 1}], poseKeypoints[{person, 10, 0}], poseKeypoints[{person, 10, 1}]);
cv::Mat leg2 = rotate(leg, leg2Angle), foot2 = rotate(foot, leg2Angle);
getPoint(point, leg.cols / 2, 0, leg2Angle, leg.cols, leg.rows, leg2.cols, leg2.rows);//获得翻转部位指定关键点
m_2x = point[0]; m_2y = point[1];
getPoint(point, leg.cols / 2, leg.rows, leg2Angle, leg.cols, leg.rows, leg2.cols, leg2.rows);//获得翻转部位指定关键点
m_3x = point[0]; m_3y = point[1];
//保存整支左腿
double f_x, f_y;
getPoint(point, foot.cols * 3 / 5 + 10 * scale, 0, leg2Angle, foot.cols, foot.rows, foot2.cols, foot2.rows);//获得翻转部位指定关键点
f_x = point[0]; f_y = point[1];
saveBead(&background, &leg1, body_3_X - body_0_X + center_x, body_3_Y - body_0_Y + center_y, m_0x, m_0y);
saveBead(&background, &leg2, m_1x - m_0x + body_3_X - body_0_X + center_x, m_1y - m_0y + body_3_Y - body_0_Y + center_y, m_2x, m_2y);
saveBead(&background, &bead, m_1x - m_0x + body_3_X - body_0_X + center_x, m_1y - m_0y + body_3_Y - body_0_Y + center_y, bead.cols / 2, bead.rows / 2);
saveBead(&background, &foot2, m_3x - m_2x + m_1x - m_0x + body_3_X - body_0_X + center_x, m_3y - m_2y + m_1y - m_0y + body_3_Y - body_0_Y + center_y, f_x, f_y);
saveBead(&background, &circle, m_3x - m_2x + m_1x - m_0x + body_3_X - body_0_X + center_x, m_3y - m_2y + m_1y - m_0y + body_3_Y - body_0_Y + center_y, circle.cols / 2, circle.rows / 2);
//右腿
//右腿的上半部分计算
double leg3Angle = getAngle(poseKeypoints[{person, 11, 0}], poseKeypoints[{person, 11, 1}], poseKeypoints[{person, 12, 0}], poseKeypoints[{person, 12, 1}]);
cv::Mat leg3 = rotate(leg, leg3Angle);
getPoint(point, leg.cols / 2, 0, leg3Angle, leg.cols, leg.rows, leg3.cols, leg3.rows);//获得翻转部位指定关键点
m_0x = point[0]; m_0y = point[1];
getPoint(point, leg.cols / 2, leg.rows, leg3Angle, leg.cols, leg.rows, leg3.cols, leg3.rows);//获得翻转部位指定关键点
m_1x = point[0]; m_1y = point[1];
//右腿的下半部分计算
double leg4Angle = getAngle(poseKeypoints[{person, 12, 0}], poseKeypoints[{person, 12, 1}], poseKeypoints[{person, 13, 0}], poseKeypoints[{person, 13, 1}]);
cv::Mat leg4 = rotate(leg, leg4Angle), foot4 = rotate(foot, leg4Angle);
getPoint(point, leg.cols / 2, 0, leg4Angle, leg.cols, leg.rows, leg4.cols, leg4.rows);//获得翻转部位指定关键点
m_2x = point[0]; m_2y = point[1];
getPoint(point, leg.cols / 2, leg.rows, leg4Angle, leg.cols, leg.rows, leg4.cols, leg4.rows);//获得翻转部位指定关键点
m_3x = point[0]; m_3y = point[1];
//保存整支右腿
getPoint(point, foot.cols * 3 / 5 + 10 * scale, 0, leg4Angle, foot.cols, foot.rows, foot4.cols, foot4.rows);//获得翻转部位指定关键点
f_x = point[0]; f_y = point[1];
saveBead(&background, &leg3, body_4_X - body_0_X + center_x, body_4_Y - body_0_Y + center_y, m_0x, m_0y);
saveBead(&background, &leg4, m_1x - m_0x + body_4_X - body_0_X + center_x, m_1y - m_0y + body_4_Y - body_0_Y + center_y, m_2x, m_2y);
saveBead(&background, &bead, m_1x - m_0x + body_4_X - body_0_X + center_x, m_1y - m_0y + body_4_Y - body_0_Y + center_y, bead.cols / 2, bead.rows / 2);
saveBead(&background, &foot4, m_3x - m_2x + m_1x - m_0x + body_4_X - body_0_X + center_x, m_3y - m_2y + m_1y - m_0y + body_4_Y - body_0_Y + center_y, f_x, f_y);
saveBead(&background, &circle, m_3x - m_2x + m_1x - m_0x + body_4_X - body_0_X + center_x, m_3y - m_2y + m_1y - m_0y + body_4_Y - body_0_Y + center_y, circle.cols / 2, circle.rows / 2);
}
return background;
}
double getAngle(double p0_x, double p0_y, double p1_x, double p1_y)
{
//这个函数用来计算关节的旋转角度,需要传入两个关键点的坐标
//我根据实际情况下确定的角度偏转方法,每个部件在竖直状态下偏转为0度,往右边偏转的角度为正值,往左边偏转的角度为负值
//考虑了斜率的正负和关键点所在的位置,进而确定是否抬手
double bodyAngle, body_X, body_Y, x, y, a;
x = p1_x - p0_x, y = -(p1_y - p0_y);
a = sqrt(x*x + y*y);
if (a == 0) bodyAngle = 0;
else if (p0_x == 0 || p0_y == 0 || p1_x == 0 || p1_y == 0) bodyAngle = 0;
else
{
bodyAngle = asin(abs(x) / a)*180.0 / pi;
if (x / y < 0)
{
if (y>0)
bodyAngle = bodyAngle - 180;
}
else
{
if (y < 0)
bodyAngle = -bodyAngle;
else
bodyAngle = 180 - bodyAngle;
}
}
return bodyAngle;
}
void saveBead(cv::Mat *background, cv::Mat *bead, int X_, int Y_, int tarX, int tarY)
{
//saveBead的作用是将部件bead保存贴在背景background上面,后面的参数是为了指定作用点,前面的X_,Y_是background的固定点坐标
//tarX和tarY是部件中的固定点坐标
if (X_ == 0 || Y_ == 0 || tarX == 0 || tarY == 0) return;
if ((X_ - tarX) >= 0 && (Y_ - tarY) >= 0)
{
if ((X_ - tarX) + (*bead).cols > (*background).cols || (Y_ - tarY) + (*bead).rows > (*background).rows) return;
if (X_ - tarX == 0 || Y_ - tarY == 0) return;
cv::Mat middle(
*background,
cvRect(
X_ - tarX,
Y_ - tarY,
(*bead).cols,
(*bead).rows
)
);
cvAdd4cMat_q(middle, *bead, 1.0);
}
else return;
}
void getPoint(int *point, int srcX, int srcY, double angle, int srcImgCols, int srcImgRows, int tarImgCols, int tarImgRows)
{
//我这里会出现原图和旋转后的图像,在原图我取其中几个点作为我想要摆放其他部件的定点,但是旋转之后这些点的坐标会发生改变
//所以我设计了getPoint函数,用于得到旋转图中,我们原来在原图中设定的点的坐标,也就是得到旋转图后我们之前想要的位置的点的坐标。
int tarX = cos(angle*(pi / 180.0))*(srcX - srcImgCols / 2)
- sin(angle*(pi / 180.0))*(-(srcY - srcImgRows / 2))
+ tarImgCols / 2,
tarY = -(sin(angle*(pi / 180.0))*(srcX - srcImgCols / 2)
+ cos(angle*(pi / 180.0))*(-(srcY - srcImgRows / 2)))
+ tarImgRows / 2;
point[0] = tarX;
point[1] = tarY;
}
int cvAdd4cMat_q(cv::Mat &dst, cv::Mat &scr, double scale)
{//图像重叠操作
if (dst.channels() != 3 || scr.channels() != 4)
{
return true;
}
if (scale < 0.01)
return false;
std::vector<cv::Mat>scr_channels;
std::vector<cv::Mat>dstt_channels;
split(scr, scr_channels);
split(dst, dstt_channels);
CV_Assert(scr_channels.size() == 4 && dstt_channels.size() == 3);
if (scale < 1)
{
scr_channels[3] *= scale;
scale = 1;
}
for (int i = 0; i < 3; i++)
{
dstt_channels[i] = dstt_channels[i].mul(255.0 / scale - scr_channels[3], scale / 255.0);
dstt_channels[i] += scr_channels[i].mul(scr_channels[3], scale / 255.0);
}
merge(dstt_channels, dst);
return true;
}
void resizeImg(double scale)
{
cv::resize(img1_s, img1, cv::Size(img1_s.cols *scale, img1_s.rows *scale), (0, 0), (0, 0), cv::INTER_LINEAR);
cv::resize(img2_s, img2, cv::Size(img2_s.cols *scale, img2_s.rows *scale), (0, 0), (0, 0), cv::INTER_LINEAR);
cv::resize(head_s, head, cv::Size(head_s.cols *scale, head_s.rows *scale), (0, 0), (0, 0), cv::INTER_LINEAR);
cv::resize(img4_s, img4, cv::Size(img4_s.cols *scale, img4_s.rows *scale), (0, 0), (0, 0), cv::INTER_LINEAR);
cv::resize(foot_s, foot, cv::Size(foot_s.cols *scale, foot_s.rows *scale), (0, 0), (0, 0), cv::INTER_LINEAR);
cv::resize(leg_s, leg, cv::Size(leg_s.cols *scale, leg_s.rows *scale), (0, 0), (0, 0), cv::INTER_LINEAR);
cv::resize(img7_s, img7, cv::Size(img7_s.cols *scale, img7_s.rows *scale), (0, 0), (0, 0), cv::INTER_LINEAR);
cv::resize(bead_s, bead, cv::Size(bead_s.cols *scale, bead_s.rows *scale), (0, 0), (0, 0), cv::INTER_LINEAR);
cv::resize(m1_s, m1, cv::Size(m1_s.cols *scale, m1_s.rows *scale), (0, 0), (0, 0), cv::INTER_LINEAR);
cv::resize(m2_s, m2, cv::Size(m2_s.cols *scale, m2_s.rows *scale), (0, 0), (0, 0), cv::INTER_LINEAR);
cv::resize(hand_s, hand, cv::Size(hand_s.cols *scale, hand_s.rows *scale), (0, 0), (0, 0), cv::INTER_LINEAR);
cv::resize(neck_s, neck, cv::Size(neck_s.cols *scale, neck_s.rows *scale), (0, 0), (0, 0), cv::INTER_LINEAR);
cv::resize(body_s, body, cv::Size(body_s.cols *scale, body_s.rows *scale), (0, 0), (0, 0), cv::INTER_LINEAR);
cv::resize(circle_s, circle, cv::Size(circle_s.cols *scale, circle_s.rows *scale), (0, 0), (0, 0), cv::INTER_LINEAR);
cv::resize(img15_s, img15, cv::Size(img15_s.cols *scale, img15_s.rows *scale), (0, 0), (0, 0), cv::INTER_LINEAR);
cv::resize(handcenter_s, handcenter, cv::Size(handcenter_s.cols *scale, handcenter_s.rows *scale), (0, 0), (0, 0), cv::INTER_LINEAR);
}
cv::Mat rotate(cv::Mat src, double angle)
{
//旋转函数,我传入图像src,并让它旋转angle角度。往右为正值,往左为负值
cv::Point2f center(src.cols / 2, src.rows / 2);
cv::Mat rot = cv::getRotationMatrix2D(center, angle, 1);
cv::Rect bbox = cv::RotatedRect(center, src.size(), angle).boundingRect();
rot.at<double>(0, 2) += bbox.width / 2.0 - center.x;
rot.at<double>(1, 2) += bbox.height / 2.0 - center.y;
cv::Mat dst;
cv::warpAffine(src, dst, rot, bbox.size());
return dst;
}
扫码查看作者更多文章
▼▼▼

(*本文为AI科技大本营转载文章,请联系作者)
◆
精彩推荐
◆
限量 5 折票开售,数量有限,扫码购买,先到先得!
推荐阅读

你点的每个“在看”,我都认真当成了AI
相关文章:

基于Idea从零搭建一个最简单的vue项目
一、需要了解的基本知识 node.js Node.js是一个Javascript运行环境(runtime),发布于2009年5月,由Ryan Dahl开发,实质是对Chrome V8引擎进行了封装。Node.js对一些特殊用例进行优化,提供替代的API,使得V8在非浏览器环境…
OpenCV中基于LBP算法的人脸检测测试代码
下面是OpenCV 3.3中基于CascadeClassifier类的LBP算法实现的人脸检测,从结果上看,不如其它开源库效果好,如libfacedetection,可参考 https://blog.csdn.net/fengbingchun/article/details/52964163 #include "funset.hpp&qu…

解决getOutputStream() has already been called for this response[java io流]
getOutputStream() has already been called for this response以上异常出现的原因和解决方法:jsp中出现此错误一般都是在jsp中使用了输出流(如输出图片验证码,文件下载等),没有妥善处理好的原因。具体的原因ÿ…
吴恩达老师深度学习视频课笔记:构建机器学习项目(机器学习策略)(1)
机器学习策略(machine learning strategy):分析机器学习问题的方法。正交化(orthogonalization):要让一个监督机器学习系统很好的工作,一般要确保四件事情,如下图:(1)、首先,你通常必须确保至少系统在训练集…

内行的AI盛会——北京智源大会带你洞见未来!(含日程及限量优惠)
报名请点击「阅读原文」北京国家会议中心2019年10月31日-11月1日www.baai.ac.cn/2019使用优惠码「BAAICSDN」专享7折优惠学生票仅69元,数量有限,先到先得世界AI看中国,中国AI看北京(长按上图或点击「阅读原文」注册参会࿰…

微软职位内部推荐-Sr. Dev Lead
微软近期Open的职位:JD如果你想试试这个职位,请跟我联系,我是微软的员工,可以做内部推荐。发你的中英文简历到我的邮箱:Nicholas.lu.mail(at)gmail.com转载于:https://www.cnblogs.com/DotNetNuke/p/3885283.html
吴恩达老师深度学习视频课笔记:构建机器学习项目(机器学习策略)(2)
进行误差分析:可进行人工统计或可同时并行评估几个想法。进行误差分析时,你应该找一组错误例子,可能在你的开发集里或者在你的测试集里,观察错误标记的例子,看看假阳性(false positives)和假阴性(false negatives)&…

3D机器人视觉在仓储物流和工业自动化领域的应用 | AI ProCon 2019
整理 | 夕颜出品 | AI科技大本营(ID:rgznai100)随着深度学习和机器学习的发展,机器人已经走出实验室,越来越多地地应用于各行各业,其中,仓储物流和工业化领域就有许多适合机器人作业的场景环境。人眼的一大…

【转载】gdi+ 内存泄漏
【转载】http://issf.blog.163.com/blog/static/1941290822009111894413472/ 最近用GDI实现了几个自定义控件,但是发现存在内存泄露问题 BOOL CGdiplusBugDlg::OnEraseBkgnd(CDC* pDC) {Image* pImage Image::FromFile(L"E:\\bac.bmp");Graphics g(pDC-&…

ubuntu fctix
感觉ubuntu自在大ibus输入法用起来实在是灰常蛋痛啊,于是乎就换了fcitx输入法(很多人推荐嘛)在安装之前先说一下fcitx输入法吧。1.添加fcitx源(官方的源是旧版,不推荐使用)fcitx的ppa源,内含fcitx和fcitx-config,使用命令sudo ged…
深度学习中的优化简介
深度学习算法在许多情况下都涉及到优化。1. 学习和纯优化有什么不同在大多数机器学习问题中,我们关注某些性能度量P,其定义于测试集上并且可能是不可解的。因此,我们只是间接地优化P。我们系统通过降低代价函数J(θ)来提高P。这一点与纯优化不…

飞凌OK6410开发板移植u-boot官方最新版u-boot-2012.10.tar.bz2
Part0 准备知识 0.1 关键参数说明 0.1.1 开发板说明 OK6410是飞凌公司发布的一款开发板,当前有2个版本,OK6410-A和OK6410-B,我当前使用的是前者;前者也经历过升级,所以有128M ram的和较新的256内存的版本,n…

参数量110亿,附赠750GB数据集,Google提NLP预训练模型T5
整理 | Just,夕颜出品 | AI科技大本营(ID:rgznai100)近日,Google 在最新一篇共有 53 页的论文《Exploring the Limits of Transfer Learning with a Unified Text-to-Text Transformer》中,提出了一个最新的预训练模型…

Linux之bash编程基本语法
在Linux运维工作中,我们为了提高工作效率通常会用bash编写脚本来完成某工作。今天就来为大家介绍bash的一些常见的基本语法。在讲解bash语法之前首先介绍一下bash。bash环境主要是由解释器来完成的。【解释器】:解释命令:词法分析、语法分析、…
深度学习中的卷积网络简介
卷积网络(convolutional network)也叫做卷积神经网络(convolutional neural network, CNN),是一种专门用来处理具有类似网格结构的数据的神经网络。例如时间序列数据(可以认为是在时间轴上有规律地采样形成的一维网格)和图像数据(可以看作是二维的像素网格)。卷积网…

Windows下搭建PHP开发环境
PHP集成开发环境有很多,如XAMPP、AppServ......只要一键安装就把PHP环境给搭建好了。但这种安装方式不够灵活,软件的自由组合不方便,同时也不利于学习。所以我还是喜欢手工搭建PHP开发环境,需要哪个模块自己安装就行了,…

大数据时代下的新生态、新洞察、新趋势 | 神策 2019 数据驱动大会
10 月 22 日,以“矩•变”为主题的神策 2019 数据驱动大会在北京维景国际大酒店顺利举行,来自全球大数据各大行业的领袖人物聚首北京,融合国际前沿技术与行业实践,深入探讨大数据时代下的新生态、新洞察、新趋势。 大会主题“矩•…

ckedit 文本编辑器
Ckeditor是一个功能非常强大的富文本编辑器,博客园有使用此编辑器,其功能完全可以与MS的Word媲美。 用起来也非常方便。下面是本人总结的安装步骤: 第一步,从http://ckeditor.com/download 下载ckeditor文件包 第二步,…

为什么我害怕数据结构学得好的程序员?
我害怕数据结构学得好的程序员,一跟他们讨论技术,我就感觉自己不是程序员,仅仅是在搬砖维持生活。我所拥有的编程技巧是什么?不就是每个程序员都会的,对数据库的增删改查吗?每一个初入职场的程序员都会。但…

Go语言基础介绍
Go是一个开源的编程语言。Go语言被设计成一门应用于搭载Web服务器,存储集群或类似用途的巨型中央服务器的系统编程语言。目前,Go最新发布版本为1.10.Go语言可以运行在Linux、FreeBSD、Mac OS X和Windows系统上。1. 结构:Go语言的基础组成有以…

强制退出WinForm程序之Application.Exit和Environment.Eixt
这几天在做一个把大量Infopath生成的XML数据,进行处理的程序,我用了MDI子窗体,每个窗体包含了各自的功能,如,遍历目录及其子目录检查文件类型并自动生成Sql语句并入库、对Infopath数据的自动检查、对数据中的某些域的替…

Oracle Study之--Oracle等待事件(3)
Oracle Study之--Oracle等待事件(3)Db file parallel read这是一个很容易引起误导的等待事件,实际上这个等待事件和并行操作(比如并行查询,并行DML)没有关系。 这个事件发生在数据库恢复的时候,…
Windows下通过Python 3.x的ctypes调用C接口
在Python中可以通过ctypes来调用动态库中的C接口,具体操作过程如下:1. 使用vs2013创建一个加、减、乘、除的动态库,并对外提供C接口,code内容如下:math_operations.hpp: #ifndef TEST_DLL_1_MATH_OPERATIONS_HPP_ #def…

如何在3天内拿下DigSci大赛的亚军?| DigSci科学数据挖掘大赛
作者 | 朱翔宇(DOTA)来源 | AI算法之心(AIHeartForYou)【导读】本文将基于作者在中国计算机大会方案宣讲PPT中的内容,给出此次比赛的完整方案,主要从赛题理解、建模核心思路、算法核心思想等角度阐述,同时对相关细节进…

如何获得Oracle系统性能统计? Oracle大型数据库系统在AIXUNIX上的实战详解 集中讨论 14...
累计读者的来信中,有若干位同仁谈到Oracle的性能问题。什么叫性能慢?怎么量化性能统计?问题我不一一列出了,下面的文字大部分来自Oracle 英文文档,我认为描述的极为精确,比我说的强。 如果有效地诊断性能问…

百度搜索查询命令——组合型
在百度上搜索一下,就会出现一堆关于查询搜索的命令,比如:site,domain,双引号,减号等等。今日,我简单总结一点组合型命令,希望对大家有帮助。 1、domain命令、减号-和的inurl组合 公式…

Delphi多线程编程中的技巧
1)创建线程 MsgThread : TMsgThread.Create(False) ; //创建并执行线程 MsgThread : TMsgThread.Create(True) ; //创建线程后挂起 constructor Create(CreateSuspended: Boolean); 中的参数CreateSuspended表示创建后是否挂起线程。 (2)设置…
Ubuntu14.04下配置OpenGL及测试代码
ubuntu14.04 64位下,默认是没有安装OpenGL相关依赖库的,若安装,则依次执行如下几条命令即可: $ sudo apt-get update $ sudo apt-get install build-essential $ sudo apt-get install libgl1-mesa-dev $ sudo apt-get install li…

VarGFaceNet:地平线提出轻量级、有效可变组卷积的人脸识别网络
作者 | Mengjia Yan、Mengao Zhao、Zining Xu、Qian Zhang、Guoli Wang、Zhizhong Su译者 | 刘畅编辑 | Jane出品 | AI科技大本营(ID:rgznai100)【导读】为了提高用于人脸识别的轻量级网络的判别和泛化能力,本文提出了一种有效的可…

【TortoiseSVN使用教程】
TortoiseSVN使用教程 TortoiseSVN是一个SVN的客户端 1.Checkout Repository 首 先要Checkout服务器端的Repository,所谓的Checkout就是指获得服务器端指定的Repository。存储的所有文件这个 Checkout和Visual Source Safe的Checkout意义完全不一样࿰…