python - 用 OpenCV + Python 拼接四个图像
问题描述
目标:
在过去的两周里,我一直在试图弄清楚如何转换以下图像:
看起来像这样的一个(可能不完全匹配,因为这张图片是在不同的时间拍摄的):
镜头校正(必要?):
我注意到的第一件事是,简单地切片图像并覆盖四个部分并不能完美地工作,因为某些线条的曲率不匹配。例如,中场线在第二个切片中向左弯曲,在第三个切片中向右弯曲。这种弯曲看起来像桶形失真,所以我尝试使用参数化镜头校正函数(将 k1、k2 和 k3 传递给 OpenCV)和lensfun。由于 lensfun 数据库不包括我的相机品牌或型号(它是 AXIS 相机),并且我不知道镜头的品牌或型号(它是作为相机的一部分制造的),我编写了一个小脚本来转储测试图像使用各种参数的各种镜头,然后浏览了数千个输出图像,直到我找到一个看起来有相对直线的图像:
此校正是使用“Samyang 12mm f/2.8 Fish-Eye ED AS NCS”镜头和 lensfun 中的“Canon EOS 10D”相机完成的。它可能并不完美,但我认为它已经足够接近第二步了。
一旦镜头畸变得到纠正,第二个问题是两个切片中的同一条线指向不同的方向,这应该通过简单的透视变换来纠正。因此,我开始了漫长的探索,以找出适合这种透视变换的参数。
失败的尝试:
1. 使用 SciPy
我首先编写了一个成本函数来判断给定参数集的“质量”(重叠像素应该匹配),然后应用 SciPy 的求解器来解决这个问题。我对我的成本函数进行了一些调整(应用高斯模糊,缩小图像,灰度缩放图像,使用 Sobel 算子获得渐变,在重叠后只查看“接缝”两侧的像素而不是整个重叠区域等),但它总是找不到一个好的解决方案。大多数时候,结果看起来都比原始相机图像差:
2. 使用数学
当失败时,我尝试应用数学来计算正确的透视变换。我知道相机的 FOV(来自规格表),我知道图像宽度和高度,我知道传感器尺寸(来自规格表),并使用量角器测量了镜头之间的角度。然后,我使用针孔模型计算了图像平面上点的预期 (x,y) 值以及校正它们所需的变换。结果看起来比 SciPy 好,但仍然令人沮丧。
3. 使用 OpenCV 的Stitcher
在此之后,我尝试使用 OpenCV 的内置Stitcher
类。然而,由于图像之间的重叠不足,它未能将切片 2 和 3 拼接在一起(大约 10% 的时间它甚至无法将切片 1 和 2 拼接在一起,大概是因为 RANSAC 的不确定性)。即使它确实成功了,针迹也不是那么好:
4. 使用 ORB 和 OpenCV 的findHomography
最近我尝试使用带有掩码的 ORB(仅在重叠区域中寻找特征)和 OpenCV 的findHomography
函数来创建自定义版本的缝合器。虽然比赛看起来很有希望,但最终的缝合仍然不是最理想的:
我开始怀疑我的方法(切片 -> 镜头正确 -> 透视变换 -> 叠加)有缺陷,并且有更好的方法来做到这一点。
5.更新ORB/findHomography
我更新了我的特征检测,以消除 Y 坐标差异很大的任何匹配项(例如,将桌子的白色与灯光的白色匹配)。这样做之后,我的匹配特征数量从 ~110 下降到 ~55,但单应性得到了显着改善。这是切片 1/2 和 2/3 的更新结果:
直到有人告诉我这一切都错了,我将继续采用以下附加步骤来执行此策略:
- 切片图像
- 镜头校正每一片
- 透视变换切片 2 或 3,使边线水平,中线垂直
- 使用 ORB + 匹配过滤 + findHomography 迭代对齐然后拼接相邻切片
最终,当一切都说完了,我想尝试计算从输入像素到输出像素的映射,这样我们就不会每帧都做所有这些复杂的工作(镜头校正、ORB、findHomography 等)。我们将为每个摄像机执行一次,将映射保存到某个文件中,然后我们可以使用实时将输入视频逐帧映射到输出视频cv2.remap
笔记:
我发布的第二张显示“预期输出”的图像直接来自有问题的相机。它可以配置为以 30 fps 的速度返回第一张图像,或以 10 fps 的速度返回第二张图像。我们希望在功能更强大的计算机上执行离机拼接,这样我们就可以获得 30 fps 但仍然有单张图像。
AXIS 提供了一个用于离机拼接的 SDK,但这个 SDK 仅适用于 Windows,我们的大部分技术堆栈都是 Linux,我们的大多数开发机器都是 Mac OS。我已经使用 Windows 计算机尝试查看他们提供的拼接 SDK,但是我没有运气让它编译和运行。他们的示例代码不断抛出错误,我从来没有运气让 Visual Studio 或 C++ 为我很好地发挥作用。
解决方案
我的建议是训练一个自动编码器。使用第一张图像作为输入,第二张图像作为输出,就像在去噪自动编码器中一样:
请注意,如果您在中间层创建的瓶颈太小,您可能会失去分辨率。
此外,变分自动编码器呈现一个潜在向量,但遵循相同的原理工作。
您可以调整此代码:
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)
推荐阅读
- r - 在公共 Y 轴上绘制 2 个折线图
- ballerina - Ballerina 找不到 JDK
- javascript - 如何实现 div 块高度的动态计算?
- sql - 如何根据两个不同表中的相反值编写 SQLite 查询?
- react-native - React Native - 即使编写了正确的代码,选择器也没有出现
- regex - RegEx - 是否可以仅使用 RegEx 引擎进行递归替换?条件搜索替换
- c# - 实例化指向另一个对象方向的预制件
- python - 如何使用正则表达式在熊猫数据框中获取字符串
- dns - 如何在网络解决方案 CNAME 中正确设置别名以使用 Amazon s3 存储桶?
- python-3.x - 如何在不停止/暂停生产者脚本的情况下释放一批 celery 后端资源?