【问题标题】:How to rotate a particular part in an image?如何旋转图像中的特定部分?
【发布时间】:2018-09-24 22:15:34
【问题描述】:

正如我在previous post 中提到的,我需要做一个应用程序来获取正面图像并将其制作为动画图像。在此主要部分是旋转头部。我使用this blog 旋转图像。

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

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

【问题讨论】:

    标签: c++ opencv image-processing computer-vision warp


    【解决方案1】:

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

    还要看看薄板样条(web 中有几种实现)和分段仿射扭曲。

    我的意思是下一步:

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

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

    对不起,俄罗斯的cmets,我翻译它们太长了:)如果你有麻烦问我,我会尽力解释。

    triangle 库的包装器:

        //#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,但它比分段仿射变形器慢。

    【讨论】:

    • 感谢您的回复。你能解释一下什么是“Inpainting”吗?我认为缺少链接?
    • docs.opencv.org/modules/photo/doc/inpainting.html 在 opencv 的示例中您可以找到 inpaint.cpp 示例。
    • 非常感谢。我会努力的,让你知道结果。我将等待一天接受这个答案,因为其他专家可以查看并发表他们的观点。
    • 正如您在尝试这种旋转之前所说的那样,我尝试使用薄板样条 (ipwithopencv.blogspot.ro/2010/01/thin-plate-spline-example.html) 和分段仿射扭曲 (code.google.com/p/imgwarp-opencv),但是当我移植到 IPAD 等设备时遇到性能问题。所以我采用了这种方法。您知道性能良好的薄板样条或分段仿射翘曲的任何实现吗?给您添麻烦了!
    • 我已经添加了一些代码来回答。有俄罗斯 cmets,但我懒得翻译它们 :) 简而言之,它的工作原理如下:1)您为每个图像分配一组点。 2) 对其中一个集合进行三角剖分,这样你就得到了一组点集的边。 3) 分别应用带有参数 s0 和 s1 的 WarpAffine 作为源图像和目标图像的点集。三角形 - 每个三角形中点的索引。我很久以前就写过它,它运行正确且速度非常快,但可能需要一些额外的边界检查。
    猜你喜欢
    • 2017-06-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-03-30
    • 1970-01-01
    • 2021-12-15
    • 2017-01-04
    • 1970-01-01
    相关资源
    最近更新 更多