0

正如我在一篇文章中提到的,我需要做一个应用程序来获取正面图像并将其制作为动画图像。在此主要部分是旋转头部。我用这个博客来旋转图像。

我所做的是选择面部区域,将其保存为图像并将其传递给旋转代码。然后我将旋转后的图像复制到原始图像。下面是旋转后的图像。

我真正需要的是避免黑色区域并进行翘曲。如何避免代码出现黑色区域​​?

在此处输入图像描述

4

1 回答 1

2

对于这种情况,您可以使用修复,我认为它会很好用。

但也可以看看薄板样条(web 中有几种实现)和分段仿射扭曲。

我的意思是接下来的步骤:

1)使网格均匀或面三角剖分,然后根据需要对网格节点进行变形(旋转面区域中的节点)。

2)应用变换(薄板样条或分段仿射扭曲)。

很抱歉俄罗斯的评论,我翻译它们太长了:) 如果你有麻烦问我,我会试着解释。

三角形库的包装:

    //#include "triangle_wrapper.h"
    #include <algorithm>
    using namespace std;
    using namespace cv;

    bool myfunction (vector<size_t> i, vector<size_t> j) 
    {
      return (i[0]==j[0] && i[1]==j[1] && i[2]==j[2]);
    }

    bool myfunction2 (vector<size_t> i) 
    {
      return (i.size()==0);
    }
    //---------------------------------------------
    // триангуляция. на входе вектор точек, на выходе вектор индексов точек по треугольникам
    //--------------------------------------------- 
    vector<vector<size_t>> Triangulate(vector<Point2d>& pts)
    {
        vector<vector<size_t>> triangles;


        struct triangulateio in, out, vorout;

        in.numberofpoints = pts.size();

        in.numberofpointattributes = 0;
        in.pointlist = (REAL *) malloc(in.numberofpoints * 2 * sizeof(REAL));
        in.pointmarkerlist = (int *) malloc(in.numberofpoints * sizeof(int));

        for(int i=0;i<pts.size();i++)
        {
            in.pointlist[2*i] = pts[i].x;
            in.pointlist[2*i+1] = pts[i].y;
            in.pointmarkerlist[i]=0;
        }
        in.numberofsegments = 0;
        in.numberofholes = 0;
        in.numberofregions = 0;

        out.pointlist = (REAL *) NULL;
        out.pointattributelist = (REAL *) NULL;
        out.pointmarkerlist = (int *) NULL;
        out.trianglelist = (int *) NULL;
        out.triangleattributelist = (REAL *) NULL;
        out.neighborlist = (int *) NULL;
        out.segmentlist = (int *) NULL;
        out.segmentmarkerlist = (int *) NULL;
        out.edgelist = (int *) NULL;
        out.edgemarkerlist = (int *) NULL;
        vorout.pointlist = (REAL *) NULL;
        vorout.pointattributelist = (REAL *) NULL;
        vorout.edgelist = (int *) NULL;
        vorout.normlist = (REAL *) NULL;

        // хелп по переключателям здесь:
        // http://www.cs.cmu.edu/~quake/triangle.switch.html

        triangulate("pczeQ", &in, &out, &vorout);

        for (int i = 0; i < out.numberoftriangles; i++) 
        {
            vector<size_t> idx(3);
            for (int j = 0; j < out.numberofcorners; j++) 
            {
                idx[j]=out.trianglelist[i * out.numberofcorners + j];
            }
            triangles.push_back(idx);
        }

        free(in.pointlist);
        free(in.pointmarkerlist);

        free(out.pointlist);
        free(out.pointattributelist);
        free(out.trianglelist);
        free(out.triangleattributelist);

        cout << "triangles.size()" <<triangles.size() << endl;

        return triangles;
    }

我的分段仿射变形器的实现:

#include "warpaffine.h"
#include <omp.h>
using namespace std;
using namespace cv;

// --------------------------------------------------------------
// Вычисление габаритного прямоугольника для точек типа Point2d
// --------------------------------------------------------------
cv::Rect_<double> boundingRect(vector<Point2d>& pts)
{
    cv::Rect_<double> r;
    double minx=FLT_MAX,maxx=FLT_MIN,miny=FLT_MAX,maxy=FLT_MIN;

    for(int i=0;i<pts.size();i++)
    {
        double px=pts[i].x;
        double py=pts[i].y;
        if(minx>px){minx=px;}
        if(miny>py){miny=py;}
        if(maxx<px){maxx=px;}
        if(maxy<py){maxy=py;}
    }

    r.x=minx;
    r.y=miny;
    r.width=maxx-minx;
    r.height=maxy-miny;

    return r;
}

// --------------------------------------------------------------
// Создаем разметку точек, по принадлежности к треугольникам
// --------------------------------------------------------------
void DrawLabelsMask(Mat& imgLabel,vector<Point2d>& points,vector<vector<size_t>>& triangles)
{
    for(int i=0;i<triangles.size();i++)
    {
        Point t[3];
        int ind1=triangles[i][0];
        int ind2=triangles[i][1];
        int ind3=triangles[i][2];
        t[0].x=cvRound(points[ind1].x);
        t[0].y=cvRound(points[ind1].y);
        t[1].x=cvRound(points[ind2].x);
        t[1].y=cvRound(points[ind2].y);
        t[2].x=cvRound(points[ind3].x);
        t[2].y=cvRound(points[ind3].y);
        cv::fillConvexPoly(imgLabel, t, 3, cv::Scalar_<int>((i+1)));
    }
}
// --------------------------------------------------------------
// Предварительный расчет коэффициентов преобразования для пар треугольников
// --------------------------------------------------------------
void CalcCoeffs(vector<Point2d>& s_0,vector<Point2d>& s_1, vector<vector<size_t>>& triangles, Mat& Coeffs)
{
    Rect_<double> Bound_0;
    Rect_<double> Bound_1;
    // Вычислили габариты
    Bound_0=boundingRect(s_0);
    Bound_1=boundingRect(s_1);
    // Предварительный расчет коэффициентов преобразования для пар треугольников
    Coeffs=Mat(triangles.size(),6,CV_64FC1);
#ifdef _OPENMP
#pragma omp parallel for
#endif
    for(int i=0;i<triangles.size();i++)
    {
        int ind1=triangles[i][0];
        int ind2=triangles[i][1];
        int ind3=triangles[i][2];
        // Исходные точки (откуда берем)
        Point2d t_0[3];
        t_0[0]=s_0[ind1]-Bound_0.tl(); // i
        t_0[1]=s_0[ind2]-Bound_0.tl(); // j
        t_0[2]=s_0[ind3]-Bound_0.tl(); // k
        // Целевые точки (куда кладем)
        Point2d t_1[3];
        t_1[0]=s_1[ind1]-Bound_1.tl(); // i
        t_1[1]=s_1[ind2]-Bound_1.tl(); // j
        t_1[2]=s_1[ind3]-Bound_1.tl(); // k

        double denom=(t_1[0].x * t_1[1].y + t_1[2].y * t_1[1].x - t_1[0].x * t_1[2].y - t_1[2].x * t_1[1].y - t_1[0].y * t_1[1].x + t_1[0].y * t_1[2].x);

        Coeffs.at<double>(i,0)= -(-t_1[2].y * t_0[1].x + t_1[2].y * t_0[0].x + t_1[1].y * t_0[2].x - t_1[1].y * t_0[0].x - t_1[0].y * t_0[2].x + t_1[0].y * t_0[1].x) / denom;
        Coeffs.at<double>(i,1)= -(t_1[2].x * t_0[1].x - t_1[2].x * t_0[0].x - t_1[1].x * t_0[2].x + t_1[1].x * t_0[0].x + t_1[0].x * t_0[2].x - t_1[0].x * t_0[1].x) / denom;
        Coeffs.at<double>(i,2)= -(t_1[2].x * t_1[1].y * t_0[0].x - t_1[2].x * t_1[0].y * t_0[1].x - t_1[1].x * t_1[2].y * t_0[0].x + t_1[1].x * t_1[0].y * t_0[2].x + t_1[0].x * t_1[2].y * t_0[1].x - t_1[0].x * t_1[1].y * t_0[2].x)/denom;
        Coeffs.at<double>(i,3)= -(t_1[1].y * t_0[2].y - t_1[0].y * t_0[2].y - t_1[2].y * t_0[1].y + t_1[2].y * t_0[0].y - t_0[0].y * t_1[1].y + t_0[1].y * t_1[0].y) / denom;
        Coeffs.at<double>(i,4)= -(-t_1[2].x * t_0[0].y + t_1[0].x * t_0[2].y + t_1[2].x * t_0[1].y - t_0[1].y * t_1[0].x - t_1[1].x * t_0[2].y + t_0[0].y * t_1[1].x) / denom;
        Coeffs.at<double>(i,5)= -(t_0[0].y * t_1[1].y * t_1[2].x - t_0[2].y * t_1[0].x * t_1[1].y - t_0[1].y * t_1[0].y * t_1[2].x + t_0[1].y * t_1[0].x * t_1[2].y + t_0[2].y * t_1[0].y * t_1[1].x - t_0[0].y * t_1[1].x * t_1[2].y) / denom;
    }
}

// --------------------------------------------------------------
// Переносит изображение из img с сеткой на точках s_0
// в изображение dst с сеткой на точках s_1
// Сетка задается треугольниками.
// triangles - вектор треугольников.
// Каждый треугольник - 3 индекса вершин.
// --------------------------------------------------------------
void WarpAffine(Mat& img,vector<Point2d>& s_0,vector<Point2d>& s_1, vector<vector<size_t>>& triangles, Mat& dstLabelsMask,Mat& dst)
{
    Rect_<double> Bound_0;
    Rect_<double> Bound_1;

    // ROI (все точки должны лежать в пределах своих изображений)
    // Вычислили габариты
    Bound_0=boundingRect(s_0);
    Bound_1=boundingRect(s_1);  

    Bound_1.width=cvRound(Bound_1.width);
    Bound_1.height=cvRound(Bound_1.height);

    Bound_0.width=cvRound(Bound_0.width);
    Bound_0.height=cvRound(Bound_0.height);

    if(Bound_0.br().x>img.cols-1){Bound_0.width=(double)img.cols-1-Bound_0.x;}
    if(Bound_0.br().y>img.rows-1){Bound_0.height=(double)img.rows-1-Bound_0.y;}


    Mat I_0=img(Bound_0);

    // Переводим координаты точек в систему координат ROI
    for(int i=0;i<s_1.size();i++)
    {
    s_1[i]-=Bound_1.tl();
    }

    // Корректируем границы
    if(Bound_1.x<0)
    {
        Bound_1.x=0;
    }

    if(Bound_1.y<0)
    {
        Bound_1.y=0;
    }

    if(Bound_1.br().x>dst.cols-1)
    {
        Bound_1.width=(double)dst.cols-1-Bound_1.x;
    }

    if(Bound_1.br().y>dst.rows-1)
    {
        Bound_1.height=(double)dst.rows-1-Bound_1.y;
    }


    // Назначаем ROI
    Mat I_1=dst(Bound_1);

    // Предварительный расчет коэффициентов преобразования для пар треугольников
    Mat Coeffs;
    CalcCoeffs(s_0,s_1,triangles,Coeffs);

    // Сканируем изображение и переносим с него точки на шаблон
    #ifdef _OPENMP
    #pragma omp parallel for
    #endif
    for(int i=0;i<I_1.rows;i++)
    {
        Point2d W(0,0);
        for(int j=0;j<I_1.cols;j++)
        {
            double x=j;
            double y=i;
            int Label=dstLabelsMask.at<int>(i,j)-1;
            if(Label!=(-1))
            {               
                W.x=Coeffs.at<double>(Label,0)*x+Coeffs.at<double>(Label,1)*y+Coeffs.at<double>(Label,2);
                W.y=Coeffs.at<double>(Label,3)*x+Coeffs.at<double>(Label,4)*y+Coeffs.at<double>(Label,5);
                if(cvRound(W.x)>0 && cvRound(W.x)<I_0.cols && cvRound(W.y)>0 && cvRound(W.y)<I_0.rows)
                {
                    I_1.at<Vec3b>(i,j)=I_0.at<Vec3b>(cvRound(W.y),cvRound(W.x));
                }
            }
        }
    }
    cv::GaussianBlur(I_1,I_1,Size(3,3),0.5);    
}

我也有 TPS,但它比分段仿射变形器慢。

于 2013-08-29T12:57:59.527 回答