OpenGL中的齐次坐标

偶然看到WikiBook上的OpenGL教程中的第一个例程给出使用openGL在显示器上画出三角形的例子:

https://gitlab.com/wikibooks-opengl/modern-tutorials/blob/master/tut01_intro-sdl2/triangle.cpp

这个例程几乎是所有OpenGL教程的Hellow World。然而,几乎所有的教程都会忽略一个非常重要的细节——即,在OpenGL中究竟是如何表示顶点坐标的。比如,上面的这段程序中,定义三角形顶点坐标的代码有两处:

第一处非常直观,在render函数中,该程序定义了在平面坐标系上的三个点:

GLfloat triangle_vertices[] = {
    0.0,  0.8,
   -0.8, -0.8,
    0.8, -0.8,
};

如果将这三个点在平面直角坐标系画出来的话,应该是这样的:

2d-triangle

另一处,这些顶点的坐标在Vertex Shader之中进行了变换:

const GLchar* vs_sources[] = {
	version,
	"attribute vec2 coord2d;                  "
	"void main(void) {                        "
	"  gl_Position = vec4(coord2d, 0.0, 1.0); "
	"}"
};

对于每一个代入到该Shader Kernel的顶点,这个计算会将二维坐标转变为一个三维空间中的齐次坐标。中文的解释非常难懂,所以,这里给出英文链接。简而言之,一个三维空间中的点,用直角坐标系表示时,这个点是一个有三个元素的向量:

\begin{bmatrix}  x \\ y \\ z  \end{bmatrix}

但是,使用齐次坐标之后,三维空间中的点需要用含有四个元素的向量表示:

\begin{bmatrix}  x\\   y\\   z\\   W  \end{bmatrix}

在齐次坐标中,如果两个坐标成比例,那么这两个坐标表示的就是同一个点;例如,(1, 2, 3, 0.5)(2, 4, 6, 1)表示的是同一个点。在这种情况下,坐标(0, 0, 0)是原点,且唯一。

当读到这段代码的时候,不禁要问,为什么在Vertext Shader中要采用如此多次一举的方式表达一个三维空间中的点?直接使用直角坐标系的(x, y, z)不是更直观吗?

之所以在图形计算中使用齐次坐标而不是直角坐标的原因在于——为了简化计算!在未加入这第四维时,三维的图形变化用矩阵表示如下:

{P}' = T + P
{P}' = S \cdot P
{P}' = R \cdot P

其中TSR分别代表平移、缩放和旋转操作。但是,这三个操作之中缩放和旋转都是矩阵乘法,仅有平移操作是加法。如果这些变换不能被统一成为同一种运算的话,用来专门做图形计算的硬件的设计复杂度就会大大增加。数学先贤们想出来的办法就是给坐标加一个维度,将以上三个操作全部统一成矩阵乘法。具体如何?Google一下就知道啦!这里是斯坦福给大答案

Debian Jessie + GCC 4.8 + NVIDIA CUDA 6.5

虽然Debian Jessie的官方库已经提供nvidia-cuda-toolkit这个包。但是,作为为东家赚钱的工具,开源社区很难得到东家的真正支持。目前这个包的版本是6.0,而Nvidia官方提供的CUDA版本已经到了7.0。另外一方面,Nvidia的Tegra K1芯片的开发板Jetson TK-1上仍然在运行6.5版的CUDA。如果你也和我一样希望在移动平台上做并行计算,那么CUDA 6.5是在百般纠结之后的唯一正确选择。

Debian Jessie已经将默认的GCC版本升级到gcc-4.9;而CUDA 6.5的编译器nvcc却只能与gcc-4.8协同工作。所以,又遇到比上一个状况更纠结的问题。

这篇文章就是记录下如何解决这些问题的方法。

第一,从NVIDIA官方下载.run的自解压文件安装CUDA 6.5。下载页面在这里:

https://developer.nvidia.com/cuda-downloads-geforce-gtx9xx

,或者直接使用下面的命令下载

# wget http://developer.download.nvidia.com/compute/cuda/6_5/rel/installers/cuda_6.5.19_linux_64.run -O /tmp/cuda_6.5.19_linux_64.run

下载完成后,运行安装程序:

# chmod +x /tmp/cuda_6.5.19_linux_64.run
# /tmp/cuda_6.5.19_linux_64.run

这个过程中安装程序会提醒系统的gcc版本不能与当前CUDA工具链一起工作,并且中止安装过程。

You are attempting to install on an unsupported configuration. Do you wish to continue? ((y)es/(n)o) [ default is no ]: 

此时只能回答“no”并且退出安装。只有检查安装LOG才能发现NVIDIA的小秘密!那就是,

使用这个参数重新运行安装程序

# /tmp/cuda_6.5.19_linux_64.run --override

安装程序还会询问安装位置,为了避免和debian的包管理冲突,我选择了/opt/nvidia/cuda-6.5。同时,安装程序还会将CUDA的样例代码安装到${HOME}

第二,在Debian Jessie上安装gcc-4.8g++-4.8并在每次编译CUDA代码是将其路径指定给编译程序。步骤如下:

# sudo apt-get install gcc-4.8 g++-4.8
# cd ${HOME}/NVIDIA_CUDA-6.5_Samples/
# GCC=g++-4.8 make

用下面的命令运行最简单的样例代码确认结果。

# ${HOME}/NVIDIA_CUDA-6.5_Samples/bin/x86_64/linux/release/simpleTexture

在OpenCV中使用视频

目前Computer Vision领域最前沿的技术都集中在视频分析上。理所当然得,OpenCV也支持USB摄像头作为一个重要的输入类别。此前,OpenCV 1.1的API就提供了cvCaptureFromCAM函数从摄像头取得数据。OpenCV 2.0开始视频输入被包装成了VideoCapture这个类。而且,使用上也更加简便。虽然看上去在OpenCV中使用从摄像头作为输入并不困难,但是因为缺乏广泛的厂商支持,这件事情在Linux操作系统上却常常让人担心。这篇文章的主旨还是将我目前的配置记录下来,以便应付我那不怎么可靠的记忆力。

硬件

虽然Linux的世界Microsoft永远不会懂,但是Linux的开发者却一直在试图弄懂Microsoft的硬件,而且结果相当不错。Microsoft的硬件LifeCam几乎可以在Debian Jessie上面完美运行!这是我使用的LifeCam Cimema(官网盗图):

Microsoft LifeCam Cinema

v4l2驱动

V4L是Linux内核为各种视频输入设备提供的驱动框架。为了使用USB接口的视频设备,在系统中还需要其他内核模块支持这些硬件。以下是必要的内核模块列表:

$ lsmod | grep v4l
v4l2_common            12995  1 videobuf2_core
videodev              126451  4 uvcvideo,v4l2_common,videobuf2_core
i2c_core               46012  5 drm,i2c_i801,nvidia,v4l2_common,videodev

除此以外,用户空间还需要像gstreamer这样的视频处理框架来支持视频的编解码和显示。如果一一安装这些软件组件,那将会是一个非常痛苦的过程。所以,为了方便起见,直接安装处于这个软件栈的最顶端的应用程序将会达到事半功倍的效果。安装下面的程序:

$ sudo apt-get install v4l2ucp guvcview

安装结束后,分层确认安装结果。使用v4l2ctrl确认视频设备的配置。

$ v4l2ctrl -s /tmp/result

这个程序会探测系统中的/dev/video0并且将其配置保存到/tmp/result。在我的LifeCam上的确认结果如下:

9963776:                     Brightness:133
9963777:                       Contrast:5
9963778:                     Saturation:83
9963788:White Balance Temperature, Auto:1
9963800:           Power Line Frequency:2
9963802:      White Balance Temperature:4500
9963803:                      Sharpness:25
9963804:         Backlight Compensation:0
10094849:                 Exposure, Auto:3
10094850:            Exposure (Absolute):156
10094856:                 Pan (Absolute):0
10094857:                Tilt (Absolute):0
10094858:               Focus (absolute):4
10094860:                    Focus, Auto:1
10094861:                 Zoom, Absolute:0

然后,使用guvcview这个应用程序来测试视频录像。

guvcview_video_capture

使用OpenCV抓取视频

#include <opencv2/opencv.hpp>

using namespace cv;

int main(int argc, char** argv)
{
  VideoCapture cap(0); // open the default camera
  if (!cap.isOpened()) // check if we succeeded
    return -1;

  Mat edges;
  namedWindow("Edges", 1);

  for (;;) {
    Mat frame;
    cap >> frame; // get a new frame from camera
    cvtColor(frame, edges, CV_BGR2GRAY);
    GaussianBlur(edges, edges, Size(7,7), 1.5, 1.5);
    Canny(edges, edges, 0, 30, 3);
    imshow("Edges", edges);
    if (waitKey(30) >= 0) break;
  }

  // the camera will be deinitialized automatically in VideoCapture
  // destructor
  return 0;
}

这段程序可以从摄像头抓取视频,并且对视频中的帧进行边缘探测。主循环使用了Canny边缘探测法。下面是运行时的截图。

OpenCv_VideoCapture_Example