【问题标题】:How to stitch images that have small overlap area and were captured with a wide angle lenses?如何拼接重叠区域较小且使用广角镜头拍摄的图像?
【发布时间】:2021-11-10 22:39:04
【问题描述】:

我有一个带有广角镜头的固定摄像头和一个移动物体的系统。当物体以恒定速度移动时,我以 10 毫米间隔和 2064x40 像素捕获图像。此外,我在没有恒定速度的情况下拍摄了 2048x40 的图像。我想拼接这些捕获的图像。

首先我参考link尝试了OpenCV的拼接方法。但是,我收到错误代码 1,并且我了解到两个图像之间没有足够的重叠区域来拼接。

在那之后,我想我可以尝试将图像连接为等速物体。我使用下面的代码,并将 13 px 作为移动参数。

我尝试过的代码:

import numpy as np
import cv2
import os

from Stitching.Blending import UVSSBlendingConcate
from Stitching.DistortionCorrection import load_coefficients


def load_images_from_folder(folder):
    print("\nImages are reading from folder: " + folder)
    images = []
    for filename in os.listdir(folder):
        img = cv2.imread((folder + "/" + filename))
        if img is not None:
            images.append(img)
    return images


def unDistortImages(images):
    mtx, dist = load_coefficients('calibration_chessboard.yml')
    for i in range(len(images)):
        images[i] = cv2.undistort(images[i], mtx, dist, None, None)
    return images


def LineTriggerConcate(dx, images, blending, IsFlip, IsUnDistorted):
    print("\nImage LineTrigger Concate Start")

    if IsUnDistorted:
        images = unDistortImages(images)

    cropped_images = []
    for i in range(len(images) - 1):
        if IsFlip is True:
            cropped_images.append(cv2.flip(images[i][2:2 + dx, 0:2064], 0))
        else:
            cropped_images.append(images[i][2:2 + dx, 0:2064])

    if not blending:
        result = cv2.vconcat(cropped_images)
        return result
    else:
        global blendingResult
        for i in range(len(cropped_images) - 1):
            if i == 0:
                blendingResult = UVSSBlendingConcate(cropped_images[i], cropped_images[i + 1], dx / 2)
            else:
                blendingResult = UVSSBlendingConcate(blendingResult, cropped_images[i + 1], dx / 2)

        print("\nImage LineTrigger Concate Finish")
        return blendingResult


def concateImages(image_list):
    image_h = cv2.vconcat(image_list)
    return image_h


def main():
    images_path = "10mm"
    image_list = load_images_from_folder(images_path)

    # LineTriggerConcate Parameters
    shiftParameter = 13
    IsBlending = False
    IsFlipped = True
    IsUnDistorted = False
    result = LineTriggerConcate(shiftParameter, image_list, IsBlending, IsFlipped, IsUnDistorted)

    cv2.imwrite(images_path + r"//" + str(shiftParameter) + r"_Shift_" + str(IsBlending) + "_Blending_Result.bmp", result)
    print('Successfully saved to %s' % images_path)


if __name__ == '__main__':
    main()

输出图像:

Result for 10mm dataset

a closer look at the problem

在上面的结果中,过渡不平滑,我尝试使用混合和不失真的方法来修复过渡,但没有成功。

另一方面,我假设物体的速度是恒定的,但不幸的是,实际情况并非如此。当物体有加速度时,图像的某些部分可能会被拉长或缩短。

有人可以建议任何方法或研究吗?
我也分享a part of 10mm intervals datasets

【问题讨论】:

  • 混合是一个孤立的问题,您可以通过不混合来解决,只需连接切片(我假设为 vconcat)。一个重复的黑色像素行似乎是由混合尝试引起的。 - 我会使用“平面”投影切片,然后尝试特征匹配并找到仿射变换(实际上只是一个翻译)。或者您可以安装某种类型的物理传感器,以稍微精确地测量物体的位置。
  • 我将从事一些使用相关性和亚像素对齐的工作...
  • 是的,对不起。切片有点太窄(并且包含固定的污垢斑点)。光学跟踪的表现低于我的希望。它也受到深度/视差的影响。这是结果(忽略懒惰的拼接)imgur.com/a/JXuHka2
  • 我每步大约 7.5 像素...或多或少
  • 我认为您的图像中仍有相当多的镜头失真。遮蔽背景(天空和汽车的眩光)以及使用校准网格(大约在起落架的距离处)改进初始单应映射都可以改进所有后续拟合策略。

标签: python opencv image-processing computer-vision image-stitching


【解决方案1】:

您当前的解决方案似乎非常接近我。您是否尝试过在合并之前使用 min diff 清理最后一点对齐?基本上创建某种形式的价值函数(可以是逐像素颜色距离的总和)。您可以将此值函数应用于图像中的单行、多行或随机散布的点。但是然后在与次图像比较时逐个像素。

这里是一些代码(sry in c++),演示了一个简单的实现(注意这只是为了与可变速度对齐,并没有纠正需要模糊的硬边缘):

#include <stdio.h>
#include <opencv2/opencv.hpp>
#include <Windows.h>
#include <string>

using namespace cv;

int main(int argc, char** argv)
{
    int compareHeight = 2;

    Mat image;
    bool firstLoop = true;
    for (int i = 140; i >=53; i--) //140
    {
        std::string fileName = "C:/Users/thoma/Downloads/stitching-main/stitching-main/dataset_10mm/"+std::to_string(i) + ".bmp";
        Mat tempImage;
        tempImage = imread(fileName, 1);
        std::cout << "imsize: " << tempImage.rows << std::endl;
        if (firstLoop) { image = tempImage; firstLoop = false; }
        else 
        {
            double lowestNorm = -1;
            int index = -1;
            Mat refSlice = image(Rect(0, image.rows- compareHeight, image.cols, compareHeight));
            for (int ii = 0; ii < tempImage.rows- compareHeight; ii++)
            {
                Mat testSlice = tempImage(Rect(0, ii, tempImage.cols, compareHeight));
                double tempNorm = cv::norm(refSlice, testSlice);
                if (lowestNorm == -1)
                {
                    lowestNorm = tempNorm;
                    index = ii;
                }
                else if (tempNorm < lowestNorm)
                {
                    lowestNorm = tempNorm;
                    index = ii;
                }
            }
            std::cout << index << " , "<<lowestNorm<< std::endl;
            index+=compareHeight;
            if (tempImage.rows - index < 1) { std::cout << "skipped" << std::endl; continue; }

            
            Mat crop_img = tempImage(Rect(0, index, tempImage.cols, tempImage.rows-index));
            vconcat(image, crop_img, image);
        }
    }

    if (!image.data)
    {
        printf("No image data \n"); system("pause");
        return -1;
    }
    namedWindow("Display Image", WINDOW_AUTOSIZE);
    imshow("Display Image", image);

    waitKey(0);
    system("pause");
    return 0;
}

在您的示例集上运行上述代码: https://imgur.com/EYvJT8I

关于您的数据集的一些附加说明: 我注意到仍然存在一些翘曲,因此通过在图像的特定条带上执行此拟合,您可以偏爱某些特征而不是其他特征。此外,我每次计算使用 2 行来尝试防止行走,您可以或多或少地使用。最后从图像中剪掉前几行可能会有所帮助,因为图像边缘似乎有一些可变的照明。稍后我可能会花更多时间在这方面,但这是我现在使用您当前的数据集的地方。

【讨论】:

    【解决方案2】:

    这里有一点“形态丰富”的解决方案:

    -将图像转换为边缘

    -扩张边缘(以提高梯度体面的灵活性)

    -找到每个图像到累积图像底部的最佳匹配偏移量

    -stash 偏移和追加累积图像

    -使用 rgb 图像和存储的偏移量重建完整图像

    #include <stdio.h>
    #include <opencv2/opencv.hpp>
    #include <Windows.h>
    #include <string>
    
    using namespace cv;
    
    double imDiff(Mat mat1, Mat mat2)
    {
        double sumSquares = 0;
        Mat channels1[3], channels2[3];
        cv::split(mat1, channels1);
        cv::split(mat2, channels2);
        Mat dif1 = channels1[0] - channels2[0];
        Mat dif2 = channels1[1] - channels2[1];
        Mat dif3 = channels1[2] - channels2[2];
        dif1.mul(dif1);
        dif2.mul(dif2);
        dif3.mul(dif3);
        sumSquares = cv::sum(dif1)[0] + cv::sum(dif2)[0] + cv::sum(dif3)[0];
        return sumSquares;
    }
    
    Mat autoCanny(Mat image)
    {
        Mat edged;
        cv::Canny(image, edged,75,125);
        Mat blurred;
    
        int dilation_size = 1;
        Mat element = cv::getStructuringElement(MORPH_CROSS,
            Size(2 * dilation_size + 1, 2 * dilation_size + 1),
            Point(dilation_size, dilation_size));
    
        cv::dilate(edged, blurred, element,cv::Point(-1,-1),2);
        //cv::GaussianBlur(edged, blurred, cv::Size(5, 5), 0);
        return blurred;
    }
    
    int main(int argc, char** argv)
    {
        int compareHeight = 25;
        int compareWidth = 350;
    
        std::vector<int> offsets = std::vector<int>();
    
        Mat image;
        bool firstLoop = true;
        for (int i = 140; i >=53; i--) //140
        {
            std::string fileName = "C:/Users/thoma/Downloads/stitching-main/stitching-main/dataset_10mm/"+std::to_string(i) + ".bmp";
            Mat tempImage = imread(fileName, 1);
            tempImage = autoCanny(tempImage);
            //std::cout << "imsize: " << tempImage.rows << std::endl;
            if (firstLoop) { image = tempImage; firstLoop = false; }
            else 
            {
                double lowestNorm = -1;
                int index = -1;
                Mat refSlice = image(Rect(image.cols/2-compareWidth, image.rows- compareHeight, 2*compareWidth, compareHeight));
                for (int ii = 0; ii < tempImage.rows- compareHeight; ii++)
                {
                    Mat testSlice = tempImage(Rect(tempImage.cols/2-compareWidth, ii, 2*compareWidth, compareHeight));
                    //double tempNorm = cv::norm(refSlice, testSlice);
                    double tempNorm = imDiff(refSlice, testSlice);
                    //std::cout << "norm: " << tempNorm << std::endl;
                    //std::cout << "cust: " << imDiff(refSlice, testSlice) << std::endl;
                    if (lowestNorm == -1)
                    {
                        lowestNorm = tempNorm;
                        index = ii;
                    }
                    else if (tempNorm < lowestNorm)
                    {
                        lowestNorm = tempNorm;
                        index = ii;
                    }
                }
                index += compareHeight;
                std::cout << tempImage.rows - index << std::endl;
                if (tempImage.rows - index < 1) { std::cout << "skipped" << std::endl; continue; }
    
                //index = 32;
                offsets.push_back(index);
                
                Mat crop_img = tempImage(Rect(0, index, tempImage.cols, tempImage.rows-index));
                vconcat(image, crop_img, image);
            }
        }
    
        namedWindow("Display Image", WINDOW_AUTOSIZE);
        imshow("Display Image", image);
    
        waitKey(0);
    
    
    
        firstLoop = true;
        int offsetIndex = 0;
        for (int i = 140; i >= 53; i--) //140
        {
            std::string fileName = "C:/Users/thoma/Downloads/stitching-main/stitching-main/dataset_10mm/" + std::to_string(i) + ".bmp";
            Mat tempImage = imread(fileName, 1);
    
            if (firstLoop) { image = tempImage; firstLoop = false; }
            else
            {
                Mat crop_img = tempImage(Rect(0, offsets[offsetIndex], tempImage.cols, tempImage.rows - offsets[offsetIndex]));
                vconcat(image, crop_img, image);
                offsetIndex++;
            }
        }
    
    
        namedWindow("Display Image", WINDOW_AUTOSIZE);
        imshow("Display Image", image);
    
        waitKey(0);
        system("pause");
        return 0;
    }
    

    结果图片: https://imgur.com/9dEXonn

    注意事项: 此方法使用图像的中心条纹。图像上似乎仍然存在向边缘增加的非平凡失真,因此该方法试图忽略这一点。这种方法容易缺乏水平边缘(很确定大多数缝合方法都会如此)。这个策略(正如我写的那样)有很多“神奇的变量”,即如果您计划部署此代码或以自动化容量运行它,您可能想要拨入和/或自动化的旋钮。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2012-07-14
      • 1970-01-01
      • 1970-01-01
      • 2017-06-12
      • 2020-03-25
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多