[英]Stitching four images with OpenCV + Python
在过去的两周里,我一直在试图弄清楚如何转换以下图像:
看起来像这样的一个(可能不完全匹配,因为这张图片是在不同的时间拍摄的):
我注意到的第一件事是,简单地切片图像并覆盖四个部分并不能完美地工作,因为某些线条的曲率不匹配。 例如,中场线在第二个切片中向左弯曲,在第三个切片中向右弯曲。 这种弯曲看起来像桶形失真,所以我尝试使用参数化镜头校正 function(将 k1、k2 和 k3 传递给 OpenCV)和lensfun 。 由于 lensfun 数据库不包括我的相机品牌或 model(它是一台 AXIS 相机),而且我不知道镜头的品牌或 model(它是作为相机的一部分制造的),我编写了一个小脚本来转储测试图像使用各种具有各种参数的镜头,然后浏览了数千张 output 图像,直到我找到一个看起来有相对直线的镜头:
此校正是使用“Samyang 12mm f/2.8 Fish-Eye ED AS NCS”镜头和 lensfun 中的“Canon EOS 10D”相机完成的。 它可能并不完美,但我认为它已经足够接近第二步了。
一旦镜头畸变得到纠正,第二个问题是两个切片中的同一条线指向不同的方向,这应该通过简单的透视变换来纠正。 因此,我开始了漫长的探索,以找出适合这种透视变换的参数。
我首先编写了一个成本 function 来判断给定参数集的“质量”(重叠像素应该匹配)并应用 SciPy 的求解器来解决这个问题。 我对我的成本 function 进行了一些调整(应用高斯模糊,缩小图像,灰度图像,使用 Sobel 算子获得渐变,在重叠后只查看“接缝”两侧的像素而不是整个重叠区域等),但它总是找不到一个好的解决方案。 大多数时候,结果看起来都比原始相机图像差:
当失败时,我尝试应用数学来计算正确的透视变换。 我知道相机的 FOV(来自规格表),我知道图像宽度和高度,我知道传感器尺寸(来自规格表),并使用 protractor 我测量了镜头之间的角度。 然后,我使用针孔 model计算了图像平面上点的预期 (x,y) 值以及校正它们所需的变换。 结果看起来比 SciPy 好,但仍然令人沮丧。
Stitcher
在此之后,我尝试使用 OpenCV 的内置Stitcher
class。 然而,由于图像之间的重叠不足,它未能将切片 2 和 3 拼接在一起(大约 10% 的时间它甚至无法将切片 1 和 2 拼接在一起,大概是因为 RANSAC 的不确定性)。 即使它确实成功了,针迹也不是那么好:
findHomography
最近我尝试使用带有掩码的 ORB(仅在重叠区域中寻找特征)和 OpenCV 的findHomography
function 来创建自定义版本的 Stitcher。 虽然比赛看起来很有希望,但最终的缝合仍然不是最理想的:
我开始怀疑我的方法(切片 -> 镜头正确 -> 透视变换 -> 叠加)有缺陷,并且有更好的方法来做到这一点。
findHomography
我更新了我的特征检测,以消除 Y 坐标差异很大的任何匹配项(例如,将桌子的白色与灯光的白色匹配)。 这样做之后,我的匹配特征数量从 ~110 下降到 ~55,但单应性得到了显着改善。 这是切片 1/2 和 2/3 的更新结果:
直到有人告诉我这一切都错了,我将继续采用以下附加步骤来执行此策略:
最终,当一切都说完了,我想尝试计算从输入像素到 output 像素的映射,这样我们就不会每帧都做所有这些复杂的工作(镜头校正、ORB、findHomography 等)。 我们将为每个摄像机执行一次,将映射保存到某个文件中,然后我们可以使用cv2.remap
将输入视频实时 map 输入到 output 视频逐帧
我发布的第二张显示“预期输出”的图像直接来自有问题的相机。 它可以配置为以 30 fps 的速度返回第一张图像,或以 10 fps 的速度返回第二张图像。 我们希望在功能更强大的计算机上执行离机拼接,这样我们就可以获得 30 fps 但仍然有单张图像。
AXIS 提供了一个 SDK 用于离机拼接,但是这个 SDK 仅适用于 Windows,我们的大部分技术堆栈是 Linux 和我们的大多数开发机器是 Mac 我已经使用 Windows 计算机尝试查看他们提供的拼接 SDK,但是我没有运气让它编译和运行。 他们的示例代码不断抛出错误,我从来没有运气让 Visual Studio 或 C++ 为我玩得很好。
我的建议是训练一个自动编码器。 使用第一张图像作为输入,第二张图像作为 output,就像在去噪自动编码器中一样:
请注意,如果您在中间层创建的瓶颈太小,您可能会失去分辨率。
此外,变分自动编码器呈现一个潜在向量,但遵循相同的原理工作。
您可以调整此代码:
denoise = Sequential()
denoise.add(Convolution2D(20, 3,3,
border_mode='valid',
input_shape=input_shape))
denoise.add(BatchNormalization(mode=2))
denoise.add(Activation('relu'))
denoise.add(UpSampling2D(size=(2, 2)))
denoise.add(Convolution2D(20, 3, 3,
init='glorot_uniform'))
denoise.add(BatchNormalization(mode=2))
denoise.add(Activation('relu'))
denoise.add(Convolution2D(20, 3, 3,init='glorot_uniform'))
denoise.add(BatchNormalization(mode=2))
denoise.add(Activation('relu'))
denoise.add(MaxPooling2D(pool_size=(3,3)))
denoise.add(Convolution2D(4, 3, 3,init='glorot_uniform'))
denoise.add(BatchNormalization(mode=2))
denoise.add(Activation('relu'))
denoise.add(Reshape((28,28,1)))
sgd = SGD(lr=learning_rate,momentum=momentum, decay=decay_rate, nesterov=False)
denoise.compile(loss='mean_squared_error', optimizer=sgd,metrics = ['accuracy'])
denoise.summary()
denoise.fit(x_train_noisy, x_train,
nb_epoch=50,
batch_size=30,verbose=1)
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.