在针孔相机模型当中, $O$ 为摄像机的光心,$z$ 轴指向相机前方,$x$ 向右,$y$ 向下。设 $P$ 点的坐标为 $[X, Y, Z]^T$, $P'$ 点的坐标为 $[X', Y', X']^T$, 并且设物理成像平面到小孔的距离为 $f$(焦距)。
那么则有
$$ \frac { Z } { f } = - \frac { X } { X ^ { \prime } } = - \frac { Y } { Y ^ { \prime } } $$
简单的, 我们可以认为
$$ \frac { Z } { f } = \frac { X } { X ^ { \prime } } = \frac { Y } { Y ^ { \prime } } $$

相机坐标 由于相机在运动,所以 $P$ 的相机坐标应该是它的世界坐标(记为 $P_w$),根据相机的当前位姿,变换到相机坐标系下的结果。
像素坐标
该点 $P$ 在像素平面上的位置 $P_{uv} = [u, v]^T$
相机坐标到像素坐标的转换
$$ \left( \begin{array} { l } u \\ v \\ 1 \end{array} \right) = \frac { 1 } { Z } \left( \begin{array} { c c c } f _ { x } & 0 & c _ { x } \\ 0 & f _ { y } & c _ { y } \\ 0 & 0 & 1 \end{array} \right) \left( \begin{array} { l } X \\ Y \\ Z \end{array} \right) \triangleq \frac { 1 } { Z } \boldsymbol { K } \boldsymbol { P } $$
把 $Z$ 移到左侧
$$ Z \left( \begin{array} { l } u \\ v \\ 1 \end{array} \right) = \left( \begin{array} { c c c } f _ { x } & 0 & c _ { x } \\ 0 & f _ { y } & c _ { y } \\ 0 & 0 & 1 \end{array} \right) \left( \begin{array} { l } X \\ Y \\ Z \end{array} \right) \triangleq \boldsymbol { K } \boldsymbol { P } $$
该式中,我们把中间的量组成的矩阵称为相机的内参数矩阵(Camera Intrinsics)$K$
世界坐标系到相机坐标系的转换
相机的位姿由它的旋转矩阵 $R$ 和平移向量 $t$ 来描述。那么有:
$$ Z P _ { u v } = Z \left[ \begin{array} { c } u \\ v \\ 1 \end{array} \right] = K \left( R P _ { w } + t \right) = K T P _ { w } $$
$$ T = \left[ \begin{array}{c} R & t \\ 0^T &1\end{array}\right] $$
由于将像素坐标表示为齐次坐标系的形式, 因此 $ZP_{uv}=P_{uv}$. 即可以把原式写成 $P _ { u v } = K T P _ { w }$
总结
#include <stdio.h>
#include <opencv2/opencv.hpp>
using namespace std;
int main(int argc, char* argv[]) {
cv::Mat image;
// 读取图片文件
string filePath = "./images/lena.png";
image = cv::imread(filePath);
// 盘算文件是否正确读取
if (image.data == nullptr){
cerr << "文件" << filePath << "不存在" << endl;
return 0;
}
// 输出文件基本信息
cout << "COL:" << image.cols << " ROW:" << image.rows << " CHANNEL:" << image.channels() << endl;
// 显示图片
cv::imshow("image", image);
cv::waitKey(0);
// 判断 image 的类型
if (image.type() != CV_8UC1 && image.type() != CV_8UC3){
// 图像类型不符合要求
cout << "image error" << endl;
return 0;
}
// 图像的随机访问
// image.at<uchar>(y, x)
chrono::steady_clock::time_point t1 = chrono::steady_clock::now();
for (size_t y=0; y<image.rows; y++){
for (size_t x = 0; x<image.cols; x++){
// 获取行指针
unsigned char *row_ptr = image.ptr(y);
unsigned char *data_ptr = &row_ptr[x*image.channels()];
for (int c=0; c!=image.channels(); c++){
unsigned char data = data_ptr[c];
}
}
}
chrono::steady_clock::time_point t2 = chrono::steady_clock::now();
chrono::duration<double> time_used = t2-t1;
cout << "Total time:" << time_used.count() << "second" << endl;
// 关于 cv::Mat 的拷贝
// 直接赋值不会复制数据
cv::Mat image_another = image;
// 直接修改 image_another 会导致 image 的变化
// 将左上角100*100的像素置为零
image_another(cv::Rect(0, 0, 100, 100)).setTo(0);
cv::imshow("image", image);
cv::waitKey(0);
// 可以使用 clone函数 来复制数据
cv::Mat image_clone = image.clone();
image_clone(cv::Rect(0, 0, 100, 100)).setTo(255);
cv::imshow("image", image);
cv::imshow("image_clone", image_clone);
cv::waitKey(0);
cv::destroyAllWindows();
return 0;
}