阅读原文

使用Opencv构建哈哈镜

  • 前言
  • 形成图像的理论
  • 如何实现
  • 创建虚拟相机
  • 定义三维曲面
  • 使用Python的VCAM库
  • 图像重映射
  • 总结

前言

在童年时期,有一个东西时常能给我带来欢乐,那就是哈哈镜。哈哈镜不是传统的平面镜,而是凹凸不平的镜面。当人们来到镜子面前时,镜子中的自己会发生奇怪的扭曲,带来欢笑声。
    这篇博文的主要动机是鼓励读者去学习相机的内外参数,相机的投影矩阵以及如何使用几何来表现图像。
    通过这篇文章,我希望读者能够了解到一个事实,那就是如果想要创建一个有趣的东西,必须要有相对应的明确的概念和理论。

形成图像的理论

简单来说,一张图像其实是一个三维世界场景的二维投影。而要将无数个三维点进行投影,需要使用下述公式:



    其中, P P P为相机的投影矩阵, X w , Y w , Z w X_w,Y_w,Z_w Xw​,Yw​,Zw​为三维空间的点坐标, u , v u,v u,v为二维空间的像素坐标。

如何实现

整个哈哈镜项目主要分为三个步骤:

  1. 创建虚拟摄像机
  2. 定义一个三维曲面(镜面),并使用合适的投影矩阵将该曲面投影到虚拟相机当中。
  3. 将三维曲面投影点的图像坐标应用于图像网格的扭曲,以获得哈哈镜的效果。

整个步骤如下图所示。

图1: 创建哈哈镜的步骤图。第一步,创建三维曲面(左图);第二步,使用虚拟相机捕获平面,得到对应的二维点(中图);第三步,将得到的二维点应用到网格变形,得到最终的效果(右图)。
    如果大伙暂时还未理解,别担心,后面会详细解释每一步。

创建虚拟相机

从上述的介绍可以知道一个三维点是如何使用投影矩阵然后得到二维坐标点的。现在让我们了解一下虚拟相机的含义,以及如何使用这个虚拟相机来拍摄图像。
    虚拟相机的本质是投影矩阵 P P P,因为它告诉我们3D世界坐标和响应的图像像素坐标之间的关系。我们知道,一个投影矩阵是由相机内参矩阵( K K K)和相机外参( M 1 M_1 M1​)矩阵组成。

import numpy as np# Tx,Ty,Tz表示虚拟相机在世界坐标系中的位置
# 定义平移矩阵
T = np.array([[1,0,0,-Tx],[0,1,0,-Ty],[0,0,1,-Tz]])# alpha,beta,gamma分别为虚拟相机的偏离方向
# 定义旋转矩阵
# x轴
Rx = np.array([[1, 0, 0], [0, math.cos(alpha), -math.sin(alpha)], [0, math.sin(alpha), math.cos(alpha)]])
# y轴
Ry = np.array([[math.cos(beta), 0, -math.sin(beta)],[0, 1, 0],[math.sin(beta),0,math.cos(beta)]])
# z轴
Rz = np.array([[math.cos(gamma), -math.sin(gamma), 0],[math.sin(gamma),math.cos(gamma), 0],[0, 0, 1]])
# 构建成最终的旋转矩阵
R = np.matmul(Rx, np.matmul(Ry, Rz))# 计算相机外参
M1 = np.matmul(R,T)# 构建相机内参矩阵
# sx和sy是x和y坐标的缩放
# ox和oy是相机光学中心的坐标
K = np.array([[-focus/sx,sh,ox],[0,focus/sy,oy],[0,0,1]])P = np.matmul(K,RT)

有了投影矩阵 P P P后,我们该如何得到图像呢?
    首先,我们假设原始图像或视频帧是三维平面。当然,我们知道实际的三维场景通常都不是平面,但这影响不大,所以可以假设场景是平面的。记住,我们的目标不是无限的模拟一个真实的哈哈镜,因此可以容忍误差。
    一旦我们将所面对的三维场景视为平面,那我们就可以简单地将世界坐标与投影矩阵相乘,得到像素坐标 ( u , v ) (u,v) (u,v)。(由于我们不考虑渲染问题,因此对于投射的光线,没做其他额外的处理)。
    总的来说,我们需要做的是捕捉(投影),首先,先将原始图像(视频帧)表示为你虚拟相机中的三维平面,然后使用投影矩阵将该平面上的每个点投影到虚拟相机的成像平面上。

定义三维曲面

对于平面镜来说,Z是一个固定的常数。而对于非平面镜来说,先构建一个关于X和Y的网格,然后在其中更新Z的值,使其发生起伏。

图2:简单的三维曲面

下面,给出如何构建一个三维曲面,并投影到相机平面的代码。

# 获取图像的大小
H,W = image.shape[:2]
# 定义x和y的坐标。这里是让图像的中心为零点
x = np.linspace(-W/2, W/2, W)
y = np.linspace(-H/2, H/2, H)# 使用x和y的坐标构建网格
xv,yv = np.meshgrid(x,y)# 扩充原有的二维网格到三维
# 设置Z为常数1,即平面
X = xv.reshape(-1,1)
Y = yv.reshape(-1,1)
Z = X*0+1
# 构建成三维世界的坐标
pts3d = np.concatenate(([X],[Y],[Z],[X*0+1]))[:,:,0]
# 使用投影矩阵将三维坐标投影到二维
pts2d = np.matmul(P,pts3d)
# 计算得到像素坐标
u = pts2d[0,:]/(pts2d[2,:]+0.00000001)
v = pts2d[1,:]/(pts2d[2,:]+0.00000001)

使用Python的VCAM库

有没有觉得上述的代码很麻烦,尤其是每次都要重复的去写。而且当我们想要动态调整一些参数时,也不好进行修改。放心好啦,大神们已经为你铺好了路。vcam库的目的在于简化创建此类三维曲面,定义虚拟摄像机,设置所有参数以及进行投影任务。
    话不多说,直接开搞。首先当然是pip先行

pip install vcam

装好vcam库之后,我们来看看他的使用。该库的原理与我们上述的内容相同,但更为但方便和简洁。

import cv2
import numpy as np
import math
from vcam import vcam,meshGen# 创建一个虚拟相机,并设定输入图像的大小
c1 = vcam(H=H,W=W)# 创建一个与输入图像大小相同的网格
plane = meshGen(H,W)# 修改Z的值,默认为1,即平面
# 将每个3D点的Z坐标定义为Z = 10*sin(2*pi[x/w]*10)
plane.Z = 10*np.sin((plane.X/plane.W)*2*np.pi*10)# 获取得到最终的三维曲面
pts3d = plane.getPlane()# 将三维曲面投影到二维图像坐标
pts2d = c1.project(pts3d)

现在得到的投影后的二维点可以用于基于网格的形变(重新映射)

图像重映射

图像重映射说得是,将输入图像的每个像素经过重映射函数的运算后,来到新的位置,进而得到一张新的图像。这一过程可以使用数学公式来表现:


这样的方法被称为前向重映射或者前向变形。其中 m a p x map_x mapx​和 m a p y map_y mapy​为每个像素点 ( x , y ) (x,y) (x,y)提供了新的位置。
    如果通过 m a p x map_x mapx​和 m a p y map_y mapy​映射和得不到有效的整数值呢?我们可以根据最接近的整数值将 ( x , y ) (x,y) (x,y)处的像素强度值扩散到相邻的像素。但是这样做,会在新得到的图像中出现孔洞。该如何避开这些孔洞呢?
    答案是,使用反向重映射(反向变形)。意思就是, m a p x map_x mapx​和 m a p y map_y mapy​将为我们提供旧图像的像素位置。它的数学公式如下:


    好了,我们现在只需要获取得到两个映射函数,即可完成有趣的图像变形啦。可是这两个函数该如何得到呢?别担心,Opencv为我们解决了一切!

# 使用投影得到的二维点集构建映射函数
# 这里的二维点集使用三维曲面投影得到
map_x,map_y = c1.getMaps(pts2d)# 将两个映射函数作用与图像,得到最终图像
output = cv2.remap(img,map_x,map_y,interpolation=cv2.INTER_LINEAR)cv2.imshow("Funny mirror",output)
cv2.waitKey(0)


图3:左图为输入图像,右图为经过变化后的图像。

总结

到这里基本上就结束啦,接下啦只要改变不同的Z的方式,就可以得到不同形变的图像了。更多的带注释的代码,可以去这里免费下载。

Learn Opencv ---- 使用Opencv构建哈哈镜相关推荐

  1. 使用OpenCV和Imutils构建图像的蒙太奇效果

    使用OpenCV和Imutils包构建图像的蒙太奇效果 1. 效果图 2. 原理 3. 源代码 参考 这篇博客将演示如何使用OpenCV和Python构建蒙太奇,以可视化一系列图像. 1. 效果图 m ...

  2. streamlit + opencv/YOLOv3 快速构建自己的图像目标检测demo网页(七)

    文章目录 1 案例介绍 2 依赖安装 3 页面使用 4 源码细节解析 4.1 直接读入markdown文件 4.2 加载文件与图片 4.3 opencv + yolov3 检测函数 系列参考: pyt ...

  3. opencv python 多帧降噪算法_使用OpenCV和Python构建自己的车辆检测模型

    介绍 我喜欢智慧城市的理念.自动智能能源系统.电网.一键接入端口的想法等等.这是一个令人着迷的概念!老实说,这是一个数据科学家的梦想,我很高兴世界上很多城市都在朝着更智能的方向发展. 智能城市的核心组 ...

  4. 基于opencv的车辆检测python_使用OpenCV和Python构建自己的车辆检测模型

    作者|PRATEEK JOSHI 编译|Flin 来源|analyticsvidhya 概述 你对智慧城市的想法感到兴奋吗?如果是的话,你会喜欢这个关于建立你自己的车辆检测系统的教程的 在深入实现部分 ...

  5. opencv 车辆识别_人工智能实战项目:使用OpenCV和Python构建自己的车辆检测模型

    总览 对智慧城市的想法感到兴奋吗?您会喜欢本教程的内容,以构建自己的车辆检测系统 在深入研究实现部分之前,我们将首先了解如何检测视频中的移动对象. 我们将使用OpenCV和Python来构建自动车辆检 ...

  6. 如何在ARM开发板上从源码编译安装OpenCV和OpenCV contrib

    点击上方"3D视觉工坊",选择"星标" 干货第一时间送达 本文主要介绍如何在ARM开发板上从源码编译安装OpenCV和OpenCV contrib. OpenC ...

  7. CV之OpenCV:OpenCV库涉及概念、常见函数、常用案例、HALCON软件简介之详细攻略

    CV之OpenCV:OpenCV库涉及概念.常见函数.常用案例.HALCON软件简介之详细攻略 目录 CV入门 OpenCV使用过程 1.基本教程 OpenCV的相关概念 1.基本概念

  8. vs2017python配置opencv_[opencv +VS2017] opencv、vs2017安装配置,环境搭建

    文章目录 [opencv] 3.4.1下载安装 一.前言 二.说明 三.下载opencv 3.1 获取链接 3.2 下载结果 四.安装opencv 五.配置opencv环境 六.vs2017 6.1下 ...

  9. 【OpenCV】OpenCV介绍及C++环境配置

    文章目录 OpenCV介绍 Windows OpenCV环境配置 OpenCV介绍 OpenCV是一个跨平台计算机视觉和机器学习软件库,可以运行在Linux.Windows.Android和Mac O ...

最新文章

  1. 怎样知道pip install 可以安装包的哪些版本
  2. .net html转义字符,asp.net转义字符
  3. 单片机小白学步系列(三) 偶遇51单片机
  4. 关于通过Sql注入直接拖数据的讨论
  5. php去除每行的重复文本,php删除文本文件中重复行的方法
  6. bootstrap源码之滚动监听组件scrollspy.js详解
  7. Cloudera Manager 术语和架构
  8. 语言nomogram校准曲线图_预测模型的概率校准
  9. java基础-01基本概念
  10. 9;XHTML 多媒体
  11. 毕业三年,同学基本都辞职了,大部分人看完很有同感~
  12. 拇指接龙游戏从WIN32向Android移植过程问题记录(1)
  13. 微信小程序 选择微信自带的地址 用户授权选择了拒绝
  14. Linux内核学习笔记(2)-- 父进程和子进程及它们的访问方法
  15. Android动态警示线,Android平台上辅助安全驾驶之线道侦测与偏离警示系统
  16. c语言中形参和实参的区别
  17. anaconda怎么打开python3.7,使用anaconda更新到python 3.7
  18. springcloud视频教程
  19. Guitar Pro新手入门教程
  20. word批量设置图片大小和对齐,使用宏定义

热门文章

  1. 免费WiFi争夺,互联网巨头在干啥?
  2. lol教育网专区服务器位置,英雄联盟排位赛竟排队三小时_lol教育网区有多少人_lol教育网大区是什么...
  3. 2019年9月8号(周日)
  4. cocos creator麻将教程系列(五)—— 达达麻将开房间
  5. 网络编程懒人入门系列目录集合
  6. 上海有哪些优秀的互联网公司?
  7. 王码星空,一场令人感伤的发布会
  8. 2021天津高考报名人数约为5.6万人,中考报名共9.9万人
  9. Java springboot Object转换List String转List 数组转List
  10. java 入门 博客园_java入门基础