我需要cameraMatrix
和distCoeff
来取消图片或点矢量。现在我想扭曲它们。
Opencv有可能吗?
我记得我在stackoverflow中读过一些关于它的东西,但现在找不到了。
编辑:我找到了在answer中完成此操作的方法。它也位于opencv开发人员专区(在此issue)
中但我的结果不正确。或多或少存在2-4像素的误差。可能我的代码有问题,因为在答案中我把单元测试中的一切看起来都很好。也许从浮动到双重输入,或者其他我看不到的东西。
这是我的测试用例:
#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <iostream>
using namespace cv;
using namespace std;
void distortPoints(const std::vector<cv::Point2d> & src, std::vector<cv::Point2d> & dst,
const cv::Mat & cameraMatrix, const cv::Mat & distorsionMatrix)
{
dst.clear();
double fx = cameraMatrix.at<double>(0,0);
double fy = cameraMatrix.at<double>(1,1);
double ux = cameraMatrix.at<double>(0,2);
double uy = cameraMatrix.at<double>(1,2);
double k1 = distorsionMatrix.at<double>(0, 0);
double k2 = distorsionMatrix.at<double>(0, 1);
double p1 = distorsionMatrix.at<double>(0, 2);
double p2 = distorsionMatrix.at<double>(0, 3);
double k3 = distorsionMatrix.at<double>(0, 4);
for (unsigned int i = 0; i < src.size(); i++)
{
const cv::Point2d & p = src[i];
double x = p.x;
double y = p.y;
double xCorrected, yCorrected;
//Step 1 : correct distorsion
{
double r2 = x*x + y*y;
//radial distorsion
xCorrected = x * (1. + k1 * r2 + k2 * r2 * r2 + k3 * r2 * r2);
yCorrected = y * (1. + k1 * r2 + k2 * r2 * r2 + k3 * r2 * r2);
//tangential distorsion
//The "Learning OpenCV" book is wrong here !!!
//False equations from the "Learning OpenCv" book below :
//xCorrected = xCorrected + (2. * p1 * y + p2 * (r2 + 2. * x * x));
//yCorrected = yCorrected + (p1 * (r2 + 2. * y * y) + 2. * p2 * x);
//Correct formulae found at : http://www.vision.caltech.edu/bouguetj/calib_doc/htmls/parameters.html
xCorrected = xCorrected + (2. * p1 * x * y + p2 * (r2 + 2. * x * x));
yCorrected = yCorrected + (p1 * (r2 + 2. * y * y) + 2. * p2 * x * y);
}
//Step 2 : ideal coordinates => actual coordinates
{
xCorrected = xCorrected * fx + ux;
yCorrected = yCorrected * fy + uy;
}
dst.push_back(cv::Point2d(xCorrected, yCorrected));
}
}
int main(int /*argc*/, char** /*argv*/) {
cout << "OpenCV version: " << CV_MAJOR_VERSION << " " << CV_MINOR_VERSION << endl; // 2 4
Mat cameraMatrix = (Mat_<double>(3,3) << 1600, 0, 789, 0, 1600, 650, 0, 0, 1);
Mat distorsion = (Mat_<double>(5,1) << -0.48, 0, 0, 0, 0);
cout << "camera matrix: " << cameraMatrix << endl;
cout << "distorsion coefficent: " << distorsion << endl;
// the starting points
std::vector<Point2f> original_pts;
original_pts.push_back( Point2f(23, 358) );
original_pts.push_back( Point2f(8, 357) );
original_pts.push_back( Point2f(12, 342) );
original_pts.push_back( Point2f(27, 343) );
original_pts.push_back( Point2f(7, 350) );
original_pts.push_back( Point2f(-8, 349) );
original_pts.push_back( Point2f(-4, 333) );
original_pts.push_back( Point2f(12, 334) );
Mat original_m = Mat(original_pts);
// undistort
Mat undistorted_m;
undistortPoints(original_m, undistorted_m,
cameraMatrix, distorsion);
cout << "undistort points" << undistorted_m << endl;
// back to array
vector< cv::Point2d > undistorted_points;
for(int i=0; i<original_pts.size(); ++i) {
Point2d p;
p.x = undistorted_m.at<float>(i, 0);
p.y = undistorted_m.at<float>(i, 1);
undistorted_points.push_back( p );
// NOTE THAT HERE THERE IS AN APPROXIMATION
// WHAT IS IT? STD::COUT? CASTING TO FLOAT?
cout << undistorted_points[i] << endl;
}
vector< cv::Point2d > redistorted_points;
distortPoints(undistorted_points, redistorted_points, cameraMatrix, distorsion);
cout << redistorted_points << endl;
for(int i=0; i<original_pts.size(); ++i) {
cout << original_pts[i] << endl;
cout << redistorted_points[i] << endl;
Point2d o;
o.x = original_pts[i].x;
o.y = original_pts[i].y;
Point2d dist = redistorted_points[i] - o;
double norm = sqrt(dist.dot(dist));
std::cout << "distance = " << norm << std::endl;
cout << endl;
}
return 0;
}
这是我的输出:
OpenCV version: 2 4
camera matrix: [1600, 0, 789;
0, 1600, 650;
0, 0, 1]
distorsion coefficent: [-0.48; 0; 0; 0; 0]
undistort points[-0.59175861, -0.22557901; -0.61276215, -0.22988389; -0.61078846, -0.24211435; -0.58972651, -0.23759322; -0.61597037, -0.23630577; -0.63910204, -0.24136727; -0.63765121, -0.25489968; -0.61291695, -0.24926868]
[-0.591759, -0.225579]
[-0.612762, -0.229884]
[-0.610788, -0.242114]
[-0.589727, -0.237593]
[-0.61597, -0.236306]
[-0.639102, -0.241367]
[-0.637651, -0.2549]
[-0.612917, -0.249269]
[24.45809095301274, 358.5558144841519; 10.15042938413364, 357.806737955385; 14.23419751024494, 342.8856229036298; 28.51642501095819, 343.610956960508; 9.353743900129871, 350.9029663678638; -4.488033489615646, 350.326357275197; -0.3050714463695385, 334.477016554487; 14.41516474594289, 334.9822130217053]
[23, 358]
[24.4581, 358.556]
distance = 1.56044
[8, 357]
[10.1504, 357.807]
distance = 2.29677
[12, 342]
[14.2342, 342.886]
distance = 2.40332
[27, 343]
[28.5164, 343.611]
distance = 1.63487
[7, 350]
[9.35374, 350.903]
distance = 2.521
[-8, 349]
[-4.48803, 350.326]
distance = 3.75408
[-4, 333]
[-0.305071, 334.477]
distance = 3.97921
[12, 334]
[14.4152, 334.982]
distance = 2.60725
答案 0 :(得分:6)
您提到的问题的答案之一中链接的initUndistortRectifyMap
确实是您想要的。由于在Remap
中使用它来构建完整的未失真图像,因此它为目标图像中的每个位置(未失真)提供了在失真图像中找到相应像素的位置,以便它们可以使用其颜色。所以它真的是f(undistorted) = distorted
地图。
但是,使用此贴图仅允许输入位置为整数且位于图像矩形内。 值得庆幸的是,文档提供了full equations。
这主要是你拥有的,除了你缺少一个初步步骤。 这是我的版本(它是C#但应该是相同的):
public PointF Distort(PointF point)
{
// To relative coordinates <- this is the step you are missing.
double x = (point.X - cx) / fx;
double y = (point.Y - cy) / fy;
double r2 = x*x + y*y;
// Radial distorsion
double xDistort = x * (1 + k1 * r2 + k2 * r2 * r2 + k3 * r2 * r2 * r2);
double yDistort = y * (1 + k1 * r2 + k2 * r2 * r2 + k3 * r2 * r2 * r2);
// Tangential distorsion
xDistort = xDistort + (2 * p1 * x * y + p2 * (r2 + 2 * x * x));
yDistort = yDistort + (p1 * (r2 + 2 * y * y) + 2 * p2 * x * y);
// Back to absolute coordinates.
xDistort = xDistort * fx + cx;
yDistort = yDistort * fy + cy;
return new PointF((float)xDistort, (float)yDistort);
}
答案 1 :(得分:3)
您可以使用ProjectPoints轻松扭曲您的观点。
cv::Mat rVec(3, 1, cv::DataType<double>::type); // Rotation vector
rVec.at<double>(0) = 0;
rVec.at<double>(1) = 0;
rVec.at<double>(2) =0;
cv::Mat tVec(3, 1, cv::DataType<double>::type); // Translation vector
tVec.at<double>(0) =0;
tVec.at<double>(1) = 0;
tVec.at<double>(2) = 0;
cv::projectPoints(points,rVec,tVec, cameraMatrix, distCoeffs,result);
PS:在opencv 3中他们添加了一个扭曲函数。
答案 2 :(得分:2)
如果将所有失真系数乘以-1,则可以将它们传递给undistort或undistortPoints,基本上你会应用反向失真,这会导致失真。
答案 3 :(得分:2)
在尝试使用本主题中的提示重新扭曲点时,我发现了一些要点:
xCorrected = x * (1. + k1 * r2 + k2 * r2 * r2 + k3 * r2 * r2 * r2); // Multiply r2 after k3 one more time in yCorrected too
// 相对坐标 <- 这是您缺少的步骤
这是错误的,因为这个问题中的代码已经使用了ы相对坐标!这是 OpenCV undistortPoints
函数中的一个技巧。它有一个新的内在矩阵作为第 6 个参数。如果它是 None ,则函数返回相对坐标中的点。这就是为什么有问题的原始代码有这一步:
//Step 2 : ideal coordinates => actual coordinates
xCorrected = xCorrected * fx + ux;
yCorrected = yCorrected * fy + uy;
当我开始研究这个问题时,我有相同的看法,即这些方程不会扭曲点,而不是相反。
最近我找到了原因。 OpenCV 的教程及其文档有不同的名称。教程使用变量“xCorrected”和“yCorrected”作为方程。虽然在文档中,相同的东西有不同的名称:“xDistorted”和“yDistorted”
那么让我来解决这个困惑:失真操作可以表示为各种失真模型中的方程。但是只有通过数值迭代算法才能实现失真。 没有解析解可以将不失真表示为方程(因为径向部分的六阶部分和非线性)
答案 4 :(得分:0)
OCV相机模型(参见http://docs.opencv.org/2.4/modules/calib3d/doc/camera_calibration_and_3d_reconstruction.html)描述了3D点首先如何映射到一个虚拟的理想针孔相机坐标,然后&#34;扭曲&#34;坐标,以便模拟真实世界相机的图像。
使用OpenCV失真系数(=棕色失真系数),以下2个操作很容易计算:
cv::undistort(....)
或cv::initUndistortRectifyMap(....)
和cv::remap(....)
的组合来完成。然而,以下两项操作在计算上要复杂得多:
cv::undistortPoints(....)
。这可能听起来很直观。更详细的解释:
对于无失真图像中给定的像素坐标,可以很容易地计算出原始图像中的相应坐标(即&#34;扭曲&#34;坐标)。
x = (u - cx) / fx; // u and v are distortion free
y = (v - cy) / fy;
rr = x*x + y*y
distortion = 1 + rr * (k1 + rr * (k2 + rr * k3))
# I ommit the tangential parameters for clarity
u_ = fx * distortion * x + cx
v_ = fy * distortion * y + cy
// u_ and v_ are coordinates in the original camera image
反过来做这件事要困难得多;基本上需要将上面的所有代码行组合成一个大的矢量方程并为u和v求解。我认为对于使用所有5个失真系数的一般情况,它只能用数字来完成。哪个(不看代码)可能是cv::undistortPoints(....)
所做的。
然而,使用失真系数,我们可以计算出从无失真图像坐标到原始摄像机图像坐标的映射的失真映射(cv::initUndistortRectifyMap(....)
)。非失真映射中的每个条目包含原始相机图像中的(浮点)像素位置。换句话说,非失真映射从无失真图像指向原始相机图像。因此,地图的计算完全按照上述公式计算。
然后可以应用地图以从原始图像(cv::remap(....)
)获取新的无失真图像。如果没有明确计算不成像图,cv::undistort()
就会这样做。
答案 5 :(得分:0)
一旦您扭曲了坐标,就没有解析解决方案,就没有办法至少在解析上返回此特定模型。它本质上是径向变形模型,其定义方式允许以简单的分析方式进行变形,反之亦然。为此,必须解决被证明没有解析解的7次多项式。
但是,径向相机模型并不特殊或不那么神圣,它只是简单的规则,可以根据拍摄照片的镜头将像素向外或向内拉伸到光学中心。越靠近光学中心,失真像素接收的失真越少。有许多其他方法可以定义径向变形模型,不仅可以产生相似的变形质量,而且可以提供定义变形逆的简单方法。但是这种方式意味着您需要自己为这种模型找到最佳参数。
例如,在我的特定情况下,我发现即使模型之间的比较,简单的S型函数(偏移和缩放比例)也能够以MSE积分误差小于或等于1E-06近似我现有的径向模型参数。似乎很尖锐。我认为原生径向模型不会产生更好的值,因此不能将其视为标准具。物理镜片的几何形状可能会以两种模型都无法代表的方式变化,为了更好地近似镜片的几何形状,应使用网状方法。但是,近似模型给我留下了深刻的印象,因为它仅使用一个自由参数并提供了非常准确的结果,这使我认为哪种模型实际上更适合工作。
这是原始径向模型(红色)的图,顶部是S型近似(绿色),以及它们的导数(蓝线):
所以在我的情况下,失真/不失真功能看起来像这样:
distort = (r, alpha) -> 2/(1 + exp(-alpha*r)) - 1
undistort = (d, alpha) -> -ln((d + 1)/(d - 1))/alpha
(请注意,失真是在光学中心附近的极坐标中执行的,并且仅影响与光学中心的距离(即不影响角度本身),r-与光学中心的距离,alpha是需要估计的自由参数):
与原始径向变形相比,这是失真的外观(绿色是近似值,红色是原始径向变形)
如果我们采用规则的像素网格并尝试使其不失真,则像素的逆映射如下所示:
答案 6 :(得分:0)
另一种方法是使用 remap 将校正后的图像投影到失真图像上:
img_distored = cv2.remap(img_rect, mapx, mapy, cv2.INTER_LINEAR)
mapx 和 mapy 是从校正像素位置到失真像素位置的映射。可以通过以下步骤获得:
X, Y = np.meshgrid(range(w), range(h)
pnts_distorted = np.merge(X, Y).reshape(w*h, 2)
pnts_rectified = cv2.undistortPoints(pnts_distorted, cameraMatrix, distort, R=rotation, P=pose)
mapx = pnts_rectified[:,:,0]
mapy = pnts_rectified[:,:,1]
cameraMatrix、畸变、旋转、姿态是cv校准和stereoRectify函数返回的参数。
答案 7 :(得分:0)
这个答案适用于希望从未扭曲的 3d 点扭曲回 2d 点的新搜索者。
对于上述情况,以下答案对我很有用..(由@Joan Charmant 回答指导。)
distortBack(point3f undistored, point2f distored_back){
double temp_x, temp_y;
temp_x = undistored.x / undistored.z;
temp_y = undistored.y / undistored.z;
double r2 = temp_x*temp_x + temp_y*temp_y;
// Radial distorsion
double xDistort = temp_x * (1 + dist_k1 * r2 + dist_k2 * r2 * r2 + dist_k3 * r2 * r2 * r2);
double yDistort = temp_y * (1 + dist_k1 * r2 + dist_k2 * r2 * r2 + dist_k3 * r2 * r2 * r2);
// Tangential distorsion
xDistort = xDistort + (2 * dist_p1 * temp_x * temp_y + dist_p2 * (r2 + 2 * temp_x * temp_x));
yDistort = yDistort + (dist_p1 * (r2 + 2 * temp_y * temp_y) + 2 * dist_p2 * temp_x * temp_y);
// Back to absolute coordinates.
distored_back.x = xDistort * camera_fx + camera_cx;
distored_back.y = yDistort * camera_fy + camera_cy;
}
欲知更多详情 - cv::UndistortPoints() 和 opencv/doc/v4.3.0
答案 8 :(得分:0)
这是一个基于 cv2.projectPoints
的实现,interpolate_missing_pixels
的源代码可以在 here 中找到。
import numpy as np
import cv2
def distort(
image: np.ndarray,
cam_matrix: np.ndarray,
dist_coefs,
**kwargs
) -> np.ndarray:
"""
Applies lens distortion to an image.
:param image: input image
:param cam_matrix: camera intrinsic matrix
:param dist_coefs must be given in opencv format
:param kwargs passed to `interpolate_missing_pixels`.
"""
rvec_tvec = np.zeros(3)
h, w = image.shape[:2]
rows, cols = np.meshgrid(np.arange(h), np.arange(w), indexing='ij')
coords_pix_orig = np.array([cols.ravel(), rows.ravel(), np.ones(h*w)])
coords_cam = np.linalg.inv(cam_matrix) @ coords_pix_orig
coords_cam = coords_cam.astype(np.float32)
coords_pix_dist, _ = cv2.projectPoints(
coords_cam, rvec_tvec, rvec_tvec, cam_matrix, dist_coefs)
coords_pix_dist = coords_pix_dist.astype(np.int).squeeze(1)
in_image =\
(coords_pix_dist[:, 0] >= 0) & (coords_pix_dist[:, 0] < w) & \
(coords_pix_dist[:, 1] >= 0) & (coords_pix_dist[:, 1] < h)
orig_r = coords_pix_orig[1][in_image].astype(np.int)
orig_c = coords_pix_orig[0][in_image].astype(np.int)
dist_r = coords_pix_dist[:, 1][in_image]
dist_c = coords_pix_dist[:, 0][in_image]
dist_image = np.zeros_like(image)
dist_image[dist_r, dist_c] = image[orig_r, orig_c]
missing_vals_mask = np.ones((h, w), dtype=np.bool)
missing_vals_mask[dist_r, dist_c] = False
interp_dist_image = interpolate_missing_pixels(
dist_image, missing_vals_mask, **kwargs
)
return interp_dist_image