如何使用OpenGL模拟OpenCV的warpPerspective功能(透视变换)

6
我已经使用Python和C++中的OpenCV进行了图像扭曲,可以看到可口可乐标志在我选择的角落处被扭曲:

final image

使用以下图像:

logo image

和这张图片:

enter image description here

这里是包含过渡图片和描述的完整相册

我需要在OpenGL中做到这一点。 我会有:

  • Corners inside which I've to map the warped image

  • A homography matrix that maps the transformation of the logo image into the logo image you see inside the final image (using OpenCV's warpPerspective), something like this:

    [[  2.59952324e+00,   3.33170976e-01,  -2.17014066e+02],
    [  8.64133587e-01,   1.82580111e+00,  -3.20053715e+02],
    [  2.78910149e-03,   4.47911310e-05,   1.00000000e+00]]
    
  • Main image (the running track image here)

  • Overlay image (the Coca Cola image here)

这可行吗?我已经阅读了很多相关的资料并开始学习OpenGL基础教程,但只凭我所学是否足够?OpenGL实现会更快,比如说大约10毫秒吗?

我正在尝试使用下面这个教程: http://ogldev.atspace.co.uk/www/tutorial12/tutorial12.html 我是不是在正确的方向上前进?我是OpenGL的新手,请谅解。谢谢。

3个回答

5

在尝试了这里和其他地方提出的许多解决方案后,我最终通过编写一个片段着色器来复制“warpPerspective”的功能来解决了这个问题。

片段着色器代码大致如下:

varying highp vec2 textureCoordinate;

uniform sampler2D inputImageTexture;

// NOTE: you will need to pass the INVERSE of the homography matrix, as well as 
// the width and height of your image as uniforms!
uniform highp mat3 inverseHomographyMatrix;
uniform highp float width;
uniform highp float height;

void main()
{
   // Texture coordinates will run [0,1],[0,1];
   // Convert to "real world" coordinates
   highp vec3 frameCoordinate = vec3(textureCoordinate.x * width, textureCoordinate.y * height, 1.0);

   // Determine what 'z' is
   highp vec3 m = inverseHomographyMatrix[2] * frameCoordinate;
   highp float zed = 1.0 / (m.x + m.y + m.z);
   frameCoordinate = frameCoordinate * zed;

   // Determine translated x and y coordinates
   highp float xTrans = inverseHomographyMatrix[0][0] * frameCoordinate.x + inverseHomographyMatrix[0][1] * frameCoordinate.y + inverseHomographyMatrix[0][2] * frameCoordinate.z;
   highp float yTrans = inverseHomographyMatrix[1][0] * frameCoordinate.x + inverseHomographyMatrix[1][1] * frameCoordinate.y + inverseHomographyMatrix[1][2] * frameCoordinate.z;

   // Normalize back to [0,1],[0,1] space
   highp vec2 coords = vec2(xTrans / width, yTrans / height);

   // Sample the texture if we're mapping within the image, otherwise set color to black
   if (coords.x >= 0.0 && coords.x <= 1.0 && coords.y >= 0.0 && coords.y <= 1.0) {
       gl_FragColor = texture2D(inputImageTexture, coords);
   } else {
       gl_FragColor = vec4(0.0,0.0,0.0,0.0);
   }
}

请注意,我们在这里传递的单应矩阵是反向单应矩阵!您必须反转传递到“warpPerspective”的单应矩阵-否则此代码将无法正常工作。
顶点着色器除了通过坐标外什么也不做:
// Vertex shader
attribute vec4 position;
attribute vec4 inputTextureCoordinate;

varying vec2 textureCoordinate;

void main() {
   // Nothing happens in the vertex shader
   textureCoordinate = inputTextureCoordinate.xy;
   gl_Position = position;
}

将未经修改的纹理坐标和位置坐标传入(即textureCoordinates =[(0,0),(0,1),(1,0),(1,1)]和positionCoordinates = [(-1,-1),(-1,1),(1,-1),(1,1)],对于三角形带),这样应该就可以工作了!


1
你能解释一下为什么需要反转单应性矩阵吗?如果输出图像的大小与源图像的大小不同,代码会在哪里改变呢? - BourbonCreams

1

[编辑] 这在我的Galaxy S9上运行良好,但在我的汽车Android上出现了整个输出纹理为白色的问题。我坚持使用原始着色器,它可以正常工作 :)

您可以在片段着色器中使用mat3*vec3操作:

varying highp vec2 textureCoordinate; 
uniform sampler2D inputImageTexture; 
uniform highp mat3 inverseHomographyMatrix; 
uniform highp float width; 
uniform highp float height; 
void main() 
{ 
    highp vec3 frameCoordinate = vec3(textureCoordinate.x * width, textureCoordinate.y * height, 1.0); 
    highp vec3 trans = inverseHomographyMatrix * frameCoordinate; 
    highp vec2 coords = vec2(trans.x / width, trans.y / height) / trans.z; 
    if (coords.x >= 0.0 && coords.x <= 1.0 && coords.y >= 0.0 && coords.y <= 1.0) { 
        gl_FragColor = texture2D(inputImageTexture, coords); 
    } else { 
        gl_FragColor = vec4(0.0,0.0,0.0,0.0); 
    } 
};

如果你想要透明背景,请不要忘记添加

GLES20.glBlendFunc(GLES20.GL_SRC_ALPHA, GLES20.GL_ONE_MINUS_SRC_ALPHA);
GLES20.glBlendEquation(GLES20.GL_FUNC_ADD);

并设置转置标志(如果您使用上述着色器):

GLES20.glUniformMatrix3fv(H_P2D, 1, true, homography, 0);

0

您可以使用texture2DProj()进行纹理的透视变形,或者通过将纹理的st坐标除以(这就是texture2DProj所做的)来使用texture2D()

请看这里:OpenGL ES 2.0中梯形的透视正确纹理贴图

warpPerspective使用矩阵投影(x,y,1)坐标,然后像texture2DProj()一样将(u,v)除以w。您需要修改矩阵,使得结果坐标被正确归一化。

就性能而言,如果您想将数据读回CPU,则瓶颈是glReadPixels。它需要多长时间取决于您的设备。如果您只是显示,假设您已经将两个纹理加载到GPU内存中,则OpenGL ES调用所需的时间要少得多,不到10毫秒。


你确定你的建议不会产生中间的图片,而是右边的那个(我想要的)吗:http://2.bp.blogspot.com/_TrRVoYy3Itc/SFF1aDGquZI/AAAAAAAAAUY/rPV86rOPljw/s400/perspective.jpg? - bad_keypoints
你说得完全正确。我会更新答案,并附上相关问题的链接。 - user1906
那个链接确实展示了我想要做的事情。现在我需要弄清楚:1)使用透视变换(以某种方式使用转换为OpenGL的单应矩阵)将logo_image转换为main_image的大小,以便其位置正确。 2)渲染两个图像(主图像和logo_warped)。 3)使用glReadPixels将final_image(我问题中的第一张图像)读取到一个OpenCV Mat对象中。 - bad_keypoints
我无法确定是否需要在OpenCV中获取相机的内部参数,以帮助我将单应矩阵分解为R、T和P矩阵(旋转、平移和透视)。有没有办法可以实现我的目标,而不需要获取相机内部参数,因为这将需要我实现相机校准,而我无法做到这一点,因为最终用户可能无法进行校准等操作。 - bad_keypoints
如果您能给我一个OpenGL应用程序的示例或样本教程,解决我的问题,那就太好了。 - bad_keypoints

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接