如何使I420图像数据不失真?有效率的
How to undistort I420 image data? Efficiently
我能够成功地消除 RGB 图像的失真。
现在,我正在处理直接不失真的 I420 数据,而不是先将其转换为 RGB。
以下是我在相机校准后遵循的步骤。
K = cv::Matx33d(541.2152931632737, 0.0, 661.7479652584254,
0.0, 541.0606969363056, 317.4524205037745,
0.0, 0.0, 1.0);
D = cv::Vec4d(-0.042166406281296365, -0.001223961942208027, -0.0017036710622692108, 0.00023929900459453295);
newSize = cv::Size(3400, 1940);
cv::Matx33d new_K;
cv::fisheye::estimateNewCameraMatrixForUndistortRectify(K, D, cv::Size(W, H), cv::Mat::eye(3, 3, CV_64F), new_K, 1, newSize); // W,H are the distorted image size
cv::fisheye::initUndistortRectifyMap(K, D, cv::Mat::eye(3, 3, CV_64F), new_K, newSize, CV_16SC2, mapx, mapy);
cv::remap(src, dst, mapx, mapy, cv::INTER_LINEAR);
以上代码成功地为我提供了未失真的图像。
现在我想恢复 I420 数据的失真。所以,现在我的 src 将是一个 I420/YV12 数据。
如何在不先将 I420 数据转换为 RGB 的情况下取消失真?
顺便说一下
I420 是一种只有 1 个通道的图像格式(与 RGB 中的 3 个通道不同)。它的高度 = 1.5*图像高度。它的宽度等于图像宽度。
下面的代码是将I420转换为BGR
cvtColor(src, BGR, CV_YUV2BGR_I420, 3);
BGR - 像素排列
I420 - 像素排列
编辑:组件在 YV12 格式中不交错,因此以下内容不起作用:
如果 YV12 数据是单通道图像,remap
操作的插值应用于所有三个 YUV 数据而不是单独的 Y、U 和 V 分量表示的值。
因此,粗略地说,而不是做一个
c.YYYYYYYY、c.UU、c.VV
它将执行
c.YYYYYYYYUUVV
在线性插值期间。
您可以在重映射后执行 YV12 -> BGR 颜色转换,但插值像素的颜色会出错。
尝试在 remap
中使用最近邻插值,而不是进行线性插值。然后你应该能够在 YV12 -> BGR 颜色转换后得到正确的颜色。
所以,找到mapx,mapy,然后remap
使用INTER_NEAREST
,最后进行YV12 -> BGR颜色转换
最有效的解决方案是调整 mapx
和 mapy
的大小并在下采样的 U 和 V 通道上应用收缩图:
- 在每个轴上将
mapx
和 mapy
缩小 x2 - 创建更小的地图矩阵。
- 将缩小贴图的所有元素除以 2(应用贴图低分辨率图像)。
- 在
Y
颜色通道上应用 mapx
和 mapy
。
- 在下采样的
U
和 V
颜色通道上应用 shrunk_mapx
和 shrunk_mapy
。
这里有一个PythonOpenCV示例代码(请阅读评论):
import cv2 as cv
import numpy as np
# For the example, read Y, U and V as separate images.
srcY = cv.imread('DistortedChessBoardY.png', cv.IMREAD_GRAYSCALE) # Y color channel (1280x720)
srcU = cv.imread('DistortedChessBoardU.png', cv.IMREAD_GRAYSCALE) # U color channel (640x360)
srcV = cv.imread('DistortedChessBoardV.png', cv.IMREAD_GRAYSCALE) # V color channel (640x360)
H, W = srcY.shape[0], srcY.shape[1]
K = np.array([[541.2152931632737, 0.0, 661.7479652584254],
[0.0, 541.0606969363056, 317.4524205037745],
[0.0, 0.0, 1.0]])
D = np.array([-0.042166406281296365, -0.001223961942208027, -0.0017036710622692108, 0.00023929900459453295])
# newSize = cv::Size(3400, 1940);
newSize = (850, 480)
# cv::Matx33d new_K;
new_K = np.eye(3)
# cv::fisheye::estimateNewCameraMatrixForUndistortRectify(K, D, cv::Size(W, H), cv::Mat::eye(3, 3, CV_64F), new_K, 1, newSize); // W,H are the distorted image size
new_K = cv.fisheye.estimateNewCameraMatrixForUndistortRectify(K, D, (W, H), np.eye(3), new_K, 1, newSize)
# cv::fisheye::initUndistortRectifyMap(K, D, cv::Mat::eye(3, 3, CV_64F), new_K, newSize, CV_16SC2, mapx, mapy);
mapx, mapy = cv.fisheye.initUndistortRectifyMap(K, D, np.eye(3), new_K, newSize, cv.CV_16SC2);
# cv::remap(src, dst, mapx, mapy, cv::INTER_LINEAR);
dstY = cv.remap(srcY, mapx, mapy, cv.INTER_LINEAR)
# Resize mapx and mapy by a factor of x2 in each axis, and divide each element in the map by 2
shrank_mapSize = (mapx.shape[1]//2, mapx.shape[0]//2)
shrunk_mapx = cv.resize(mapx, shrank_mapSize, interpolation = cv.INTER_LINEAR) // 2
shrunk_mapy = cv.resize(mapy, shrank_mapSize, interpolation = cv.INTER_LINEAR) // 2
# Remap U and V using shunk maps
dstU = cv.remap(srcU, shrunk_mapx, shrunk_mapy, cv.INTER_LINEAR, borderValue=128)
dstV = cv.remap(srcV, shrunk_mapx, shrunk_mapy, cv.INTER_LINEAR, borderValue=128)
cv.imshow('dstY', dstY)
cv.imshow('dstU', dstU)
cv.imshow('dstV', dstV)
cv.waitKey(0)
cv.destroyAllWindows()
结果:
Y:
你:
V:
转换为 RGB 后:
C++ 实现注意事项:
由于I420格式在内存中将Y、U、V排列成3个连续的平面,所以很简单,给每个"plane"设置一个指针,把它当作一个灰度图像。
相同的数据排序适用于输出图像 - 将 3 个指针设置为输出 "planes"。
插图(假设宽度和高度相等,并假设字节步长等于宽度):
srcY -> YYYYYYYY dstY -> YYYYYYYYYYYY
YYYYYYYY YYYYYYYYYYYY
YYYYYYYY YYYYYYYYYYYY
YYYYYYYY YYYYYYYYYYYY
YYYYYYYY remap YYYYYYYYYYYY
YYYYYYYY ======> YYYYYYYYYYYY
srcU -> UUUU YYYYYYYYYYYY
UUUU dstU -> YYYYYYYYYYYY
UUUU UUUUUU
srcV -> VVVV UUUUUU
VVVV UUUUUU
VVVV UUUUUU
dstV -> VVVVVV
VVVVVV
VVVVVV
VVVVVV
上图的实现是C++
假设宽高是偶数,字节跨度等于宽,可以使用下面的C++例子将I420转换成Y、U、V平面:
假设:srcI420
是 I420 格式的 Wx(H*3/2)
矩阵,如 cv::Mat srcI420(cv::Size(W, H * 3 / 2), CV_8UC1);
。
int W = 1280, H = 720; //Assume resolution of Y plane is 1280x720
//Pointer to Y plane
unsigned char *pY = (unsigned char*)srcI420.data;
//Y plane as cv::Mat, resolution of srcY is 1280x720
cv::Mat srcY = cv::Mat(cv::Size(W, H), CV_8UC1, (void*)pY);
//U plane as cv::Mat, resolution of srcU is 640x360 (in memory buffer, U plane is placed after Y).
cv::Mat srcU = cv::Mat(cv::Size(W/2, H/2), CV_8UC1, (void*)(pY + W*H));
//V plane as cv::Mat, resolution of srcV is 640x360 (in memory buffer, V plane is placed after U).
cv::Mat srcV = cv::Mat(cv::Size(W / 2, H / 2), CV_8UC1, (void*)(pY + W*H + (W/2*H/2)));
//Display srcY, srcU, srcV for testing
cv::imshow("srcY", srcY);
cv::imshow("srcU", srcU);
cv::imshow("srcV", srcV);
cv::waitKey(0);
以上示例使用指针操作,无需复制数据。
您可以对目标 I420 图像使用相同的指针操作。
注意:该解决方案在大多数情况下都有效,但不能保证在所有情况下都有效。
我能够成功地消除 RGB 图像的失真。
现在,我正在处理直接不失真的 I420 数据,而不是先将其转换为 RGB。
以下是我在相机校准后遵循的步骤。
K = cv::Matx33d(541.2152931632737, 0.0, 661.7479652584254,
0.0, 541.0606969363056, 317.4524205037745,
0.0, 0.0, 1.0);
D = cv::Vec4d(-0.042166406281296365, -0.001223961942208027, -0.0017036710622692108, 0.00023929900459453295);
newSize = cv::Size(3400, 1940);
cv::Matx33d new_K;
cv::fisheye::estimateNewCameraMatrixForUndistortRectify(K, D, cv::Size(W, H), cv::Mat::eye(3, 3, CV_64F), new_K, 1, newSize); // W,H are the distorted image size
cv::fisheye::initUndistortRectifyMap(K, D, cv::Mat::eye(3, 3, CV_64F), new_K, newSize, CV_16SC2, mapx, mapy);
cv::remap(src, dst, mapx, mapy, cv::INTER_LINEAR);
以上代码成功地为我提供了未失真的图像。
现在我想恢复 I420 数据的失真。所以,现在我的 src 将是一个 I420/YV12 数据。 如何在不先将 I420 数据转换为 RGB 的情况下取消失真?
顺便说一下 I420 是一种只有 1 个通道的图像格式(与 RGB 中的 3 个通道不同)。它的高度 = 1.5*图像高度。它的宽度等于图像宽度。
下面的代码是将I420转换为BGR
cvtColor(src, BGR, CV_YUV2BGR_I420, 3);
BGR - 像素排列
编辑:组件在 YV12 格式中不交错,因此以下内容不起作用:
如果 YV12 数据是单通道图像,remap
操作的插值应用于所有三个 YUV 数据而不是单独的 Y、U 和 V 分量表示的值。
因此,粗略地说,而不是做一个
c.YYYYYYYY、c.UU、c.VV
它将执行
c.YYYYYYYYUUVV
在线性插值期间。
您可以在重映射后执行 YV12 -> BGR 颜色转换,但插值像素的颜色会出错。
尝试在 remap
中使用最近邻插值,而不是进行线性插值。然后你应该能够在 YV12 -> BGR 颜色转换后得到正确的颜色。
所以,找到mapx,mapy,然后remap
使用INTER_NEAREST
,最后进行YV12 -> BGR颜色转换
最有效的解决方案是调整 mapx
和 mapy
的大小并在下采样的 U 和 V 通道上应用收缩图:
- 在每个轴上将
mapx
和mapy
缩小 x2 - 创建更小的地图矩阵。 - 将缩小贴图的所有元素除以 2(应用贴图低分辨率图像)。
- 在
Y
颜色通道上应用mapx
和mapy
。 - 在下采样的
U
和V
颜色通道上应用shrunk_mapx
和shrunk_mapy
。
这里有一个PythonOpenCV示例代码(请阅读评论):
import cv2 as cv
import numpy as np
# For the example, read Y, U and V as separate images.
srcY = cv.imread('DistortedChessBoardY.png', cv.IMREAD_GRAYSCALE) # Y color channel (1280x720)
srcU = cv.imread('DistortedChessBoardU.png', cv.IMREAD_GRAYSCALE) # U color channel (640x360)
srcV = cv.imread('DistortedChessBoardV.png', cv.IMREAD_GRAYSCALE) # V color channel (640x360)
H, W = srcY.shape[0], srcY.shape[1]
K = np.array([[541.2152931632737, 0.0, 661.7479652584254],
[0.0, 541.0606969363056, 317.4524205037745],
[0.0, 0.0, 1.0]])
D = np.array([-0.042166406281296365, -0.001223961942208027, -0.0017036710622692108, 0.00023929900459453295])
# newSize = cv::Size(3400, 1940);
newSize = (850, 480)
# cv::Matx33d new_K;
new_K = np.eye(3)
# cv::fisheye::estimateNewCameraMatrixForUndistortRectify(K, D, cv::Size(W, H), cv::Mat::eye(3, 3, CV_64F), new_K, 1, newSize); // W,H are the distorted image size
new_K = cv.fisheye.estimateNewCameraMatrixForUndistortRectify(K, D, (W, H), np.eye(3), new_K, 1, newSize)
# cv::fisheye::initUndistortRectifyMap(K, D, cv::Mat::eye(3, 3, CV_64F), new_K, newSize, CV_16SC2, mapx, mapy);
mapx, mapy = cv.fisheye.initUndistortRectifyMap(K, D, np.eye(3), new_K, newSize, cv.CV_16SC2);
# cv::remap(src, dst, mapx, mapy, cv::INTER_LINEAR);
dstY = cv.remap(srcY, mapx, mapy, cv.INTER_LINEAR)
# Resize mapx and mapy by a factor of x2 in each axis, and divide each element in the map by 2
shrank_mapSize = (mapx.shape[1]//2, mapx.shape[0]//2)
shrunk_mapx = cv.resize(mapx, shrank_mapSize, interpolation = cv.INTER_LINEAR) // 2
shrunk_mapy = cv.resize(mapy, shrank_mapSize, interpolation = cv.INTER_LINEAR) // 2
# Remap U and V using shunk maps
dstU = cv.remap(srcU, shrunk_mapx, shrunk_mapy, cv.INTER_LINEAR, borderValue=128)
dstV = cv.remap(srcV, shrunk_mapx, shrunk_mapy, cv.INTER_LINEAR, borderValue=128)
cv.imshow('dstY', dstY)
cv.imshow('dstU', dstU)
cv.imshow('dstV', dstV)
cv.waitKey(0)
cv.destroyAllWindows()
结果:
Y:
你:
V:
转换为 RGB 后:
C++ 实现注意事项:
由于I420格式在内存中将Y、U、V排列成3个连续的平面,所以很简单,给每个"plane"设置一个指针,把它当作一个灰度图像。
相同的数据排序适用于输出图像 - 将 3 个指针设置为输出 "planes"。
插图(假设宽度和高度相等,并假设字节步长等于宽度):
srcY -> YYYYYYYY dstY -> YYYYYYYYYYYY
YYYYYYYY YYYYYYYYYYYY
YYYYYYYY YYYYYYYYYYYY
YYYYYYYY YYYYYYYYYYYY
YYYYYYYY remap YYYYYYYYYYYY
YYYYYYYY ======> YYYYYYYYYYYY
srcU -> UUUU YYYYYYYYYYYY
UUUU dstU -> YYYYYYYYYYYY
UUUU UUUUUU
srcV -> VVVV UUUUUU
VVVV UUUUUU
VVVV UUUUUU
dstV -> VVVVVV
VVVVVV
VVVVVV
VVVVVV
上图的实现是C++
假设宽高是偶数,字节跨度等于宽,可以使用下面的C++例子将I420转换成Y、U、V平面:
假设:srcI420
是 I420 格式的 Wx(H*3/2)
矩阵,如 cv::Mat srcI420(cv::Size(W, H * 3 / 2), CV_8UC1);
。
int W = 1280, H = 720; //Assume resolution of Y plane is 1280x720
//Pointer to Y plane
unsigned char *pY = (unsigned char*)srcI420.data;
//Y plane as cv::Mat, resolution of srcY is 1280x720
cv::Mat srcY = cv::Mat(cv::Size(W, H), CV_8UC1, (void*)pY);
//U plane as cv::Mat, resolution of srcU is 640x360 (in memory buffer, U plane is placed after Y).
cv::Mat srcU = cv::Mat(cv::Size(W/2, H/2), CV_8UC1, (void*)(pY + W*H));
//V plane as cv::Mat, resolution of srcV is 640x360 (in memory buffer, V plane is placed after U).
cv::Mat srcV = cv::Mat(cv::Size(W / 2, H / 2), CV_8UC1, (void*)(pY + W*H + (W/2*H/2)));
//Display srcY, srcU, srcV for testing
cv::imshow("srcY", srcY);
cv::imshow("srcU", srcU);
cv::imshow("srcV", srcV);
cv::waitKey(0);
以上示例使用指针操作,无需复制数据。
您可以对目标 I420 图像使用相同的指针操作。
注意:该解决方案在大多数情况下都有效,但不能保证在所有情况下都有效。