在过去的两个星期中,我一直在尝试找出如何转换以下图像:
对于一个看起来像这样的人(可能不完全匹配,因为这张图片是在其他时间拍摄的):
我注意到的第一件事是,简单地将图像切片并覆盖四个部分并不能完美地工作,因为某些线条的曲率不匹配。例如,中线在第二个切片中向左弯曲,在第三个切片中向右弯曲。这种弯曲看起来像barrel distortion,所以我尝试同时使用参数化的镜头校正功能(将k1,k2和k3传递给OpenCV)和lensfun。由于lensfun数据库不包含我的相机品牌或型号(它是AXIS相机),并且我不知道镜头的品牌或型号(它是作为相机的一部分制造的),因此我编写了一个小脚本来使用各种具有各种参数的镜头,然后浏览 数千 的输出图像,直到发现看起来像是相对直线的镜头为止。
此校正是通过在Lensfun中使用“ Samyang 12mm f / 2.8 Fish-Eye ED AS NCS”镜头和“ Canon EOS 10D”相机进行的。它可能不是完美,但我认为它已经足够接近第二步了。
一旦校正了镜头畸变,第二个问题是两个切片中的同一条线指向不同的方向,应使用简单的透视变换对其进行校正。因此,我开始了漫长的探索,以找出用于此透视变换的适当参数。
我首先编写了一个成本函数来判断给定参数集(重叠像素应匹配)的“质量”,然后应用SciPy的求解器进行求解。我对成本函数进行了一些调整(应用高斯模糊,按比例缩小图像,对图像进行灰度缩放,使用Sobel运算符获得渐变),重叠后仅查看“接缝”两侧的像素,而不是整个重叠区域等),但始终找不到好的解决方案。在大多数情况下,结果看上去都比原始相机图像差:
当失败时,我尝试将数学应用于 compute 正确的透视变换。我知道相机的FOV(根据规格表),我知道图像的宽度和高度,我知道传感器的尺寸(根据规格表),并使用量角器测量了镜头之间的角度。然后,我使用pinhole model计算了图像平面上点的预期(x,y)值,以及需要进行哪些变换才能对其进行校正。结果看起来比SciPy更好,但仍然令人沮丧。
Stitcher
此后,我尝试使用OpenCV的内置Stitcher
类。但是,由于图像之间的重叠不足,无法将切片2和3缝合在一起(大约10%的时间甚至无法将切片1和2缝合在一起,大概是由于RANSAC的不确定性)。即使成功了,缝线也不是那么好:
findHomography
最近,我尝试使用带遮罩的ORB(仅在重叠区域中查找特征)和OpenCV的findHomography
函数来创建自定义版本的Stitcher。尽管比赛看起来很有希望,但最终的缝制仍然不是最佳的:
我开始怀疑我的方法(切片->镜头校正->透视变换->覆盖)存在缺陷,并且有更好的方法可以做到这一点。
findHomography
我更新了特征检测功能,以消除Y坐标显着不同的任何匹配项(例如,将表格的白色与灯光的白色进行匹配)。完成此操作后,我的匹配特征数量从〜110降至〜55,但是单应性得到了显着改善。这是切片1/2和2/3更新后的针迹:
在有人可以告诉我我将要解决所有错误之前,我将继续执行此策略,并增加以下步骤:
说到最后,我想尝试计算从输入像素到输出像素的映射,这样我们就不会每帧进行所有这些复杂的工作(镜头校正,ORB,findHomography等)。我们将为每个摄像机执行一次,将映射保存到文件中的某个位置,然后可以使用cv2.remap
我发布的第二张图片显示了“预期的输出”,直接来自相关相机。它可以配置为以30 fps返回第一张图像,或以10 fps返回第二张图像。我们希望在一台功能更强大的计算机上执行机外拼接,以便我们可以获得30 fps的帧率,但仍然只有一张图像。
AXIS提供了一个用于在相机外进行拼接的SDK,但是该SDK仅适用于Windows,我们的大多数技术堆栈是Linux,而我们的大多数开发计算机是Mac OS。我曾经使用Windows计算机尝试研究它们提供的拼接SDK,但是我没有运气来编译和运行它。他们的示例代码不断抛出错误,而让Visual Studio或C ++玩起来对我来说真是太幸运了。
答案 0 :(得分:1)
我的建议是训练自动编码器。将第一个图像用作输入,将第二个图像用作输出,如去噪自动编码器:
请注意,如果在中间层创建的Botteleneck太小,则可能会失去分辨率。
此外,变分自动编码器提供了一个潜在矢量,但遵循相同的原理。
您可以修改以下代码:
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)