Перспективное сшивание изображений

Я нашел очень полезный пример сшивания изображений, но моя проблема заключалась в том, что этот тип изображений здесь является примером Первое изображение

а вот другое изображение Второе изображение

когда я использую сшиватель opencv, полученные изображения становятся меньше, как это Малый результат

есть ли способ применить преобразование к входным изображениям, чтобы они были похожи на этот введите здесь описание изображения

вот код

#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include<opencv2/opencv.hpp>
#include <opencv2/features2d/features2d.hpp>
#include <opencv2/nonfree/nonfree.hpp>
#include <opencv2/stitching/stitcher.hpp>
#include<vector>
using namespace cv;
using namespace std;
cv::vector<cv::Mat> ImagesList;
string result_name ="/TopViewsHorizantale/1.bmp";
int main()
{
      // Load the images

 Mat image1= imread("current_00000.bmp" );
 Mat image2= imread("current_00001.bmp" );
 cv::resize(image1, image1, image2.size());
 Mat gray_image1;
 Mat gray_image2;
 Mat Matrix = Mat(3,3,CV_32FC1);

 // Convert to Grayscale
 cvtColor( image1, gray_image1, CV_RGB2GRAY );
 cvtColor( image2, gray_image2, CV_RGB2GRAY );
 namedWindow("first image",WINDOW_AUTOSIZE);
 namedWindow("second image",WINDOW_AUTOSIZE);
 imshow("first image",image2);
 imshow("second image",image1);

if( !gray_image1.data || !gray_image2.data )
 { std::cout<< " --(!) Error reading images " << std::endl; return -1; }

//-- Step 1: Detect the keypoints using SURF Detector
 int minHessian = 400;

SurfFeatureDetector detector( minHessian );

std::vector< KeyPoint > keypoints_object, keypoints_scene;

detector.detect( gray_image1, keypoints_object );
detector.detect( gray_image2, keypoints_scene );

//-- Step 2: Calculate descriptors (feature vectors)
 SurfDescriptorExtractor extractor;

Mat descriptors_object, descriptors_scene;

extractor.compute( gray_image1, keypoints_object, descriptors_object );
extractor.compute( gray_image2, keypoints_scene, descriptors_scene );

//-- Step 3: Matching descriptor vectors using FLANN matcher
 FlannBasedMatcher matcher;
 std::vector< DMatch > matches;
 matcher.match( descriptors_object, descriptors_scene, matches );

double max_dist = 0; double min_dist = 100;

//-- Quick calculation of max and min distances between keypoints
 for( int i = 0; i < descriptors_object.rows; i++ )
 { double dist = matches[i].distance;
 if( dist < min_dist ) min_dist = dist;
 if( dist > max_dist ) max_dist = dist;
 }

printf("-- Max dist : %f \n", max_dist );
printf("-- Min dist : %f \n", min_dist );

//-- Use only "good" matches (i.e. whose distance is less than 3*min_dist )
 std::vector< DMatch > good_matches;

for( int i = 0; i < descriptors_object.rows; i++ )
 { if( matches[i].distance < 3*min_dist )
 { good_matches.push_back( matches[i]); }
 }
 std::vector< Point2f > obj;
 std::vector< Point2f > scene;

for( int i = 0; i < good_matches.size(); i++ )
 {
 //-- Get the keypoints from the good matches
 obj.push_back( keypoints_object[ good_matches[i].queryIdx ].pt );
 scene.push_back( keypoints_scene[ good_matches[i].trainIdx ].pt );
 }

// Find the Homography Matrix
 Mat H = findHomography( obj, scene, CV_RANSAC );
 // Use the Homography Matrix to warp the images
 cv::Mat result;
      int N = image1.rows + image2.rows;
 int M = image1.cols+image2.cols;
 warpPerspective(image1,result,H,cv::Size(N,M));
 cv::Mat half(result,cv::Rect(0,0,image2.rows,image2.cols));
 result.copyTo(half);
 namedWindow("Result",WINDOW_AUTOSIZE);
 imshow( "Result", result);

 imwrite(result_name, result);

 waitKey(0);
 return 0;
}

Также здесь есть ссылка на некоторые изображения:: https://www.dropbox.com/sh/ovzkqomxvzw8rww/AAB2DDCrCF6NlCFre7V1Gb6La?dl=0 Большое спасибо, Лафи.


person lafi raed    schedule 09.10.2016    source источник
comment
Пожалуйста, объясните свой вопрос более подробно? Как записывается это видео? Почему камера движется по диагонали и т.д.   -  person saurabheights    schedule 09.10.2016
comment
камера может вращаться и записывать видео, ее можно поворачивать под любым углом, и мы должны склеить все изображения   -  person lafi raed    schedule 09.10.2016
comment
проблема в том, что при сшивании этих изображений результирующие изображения становятся все меньше и меньше   -  person lafi raed    schedule 09.10.2016
comment
Есть ли у вас доступ к матрице гомографии между текущим кадром и предыдущим кадром? Вы используете поток изображений и сшиваете Сшитый кадр и Текущий кадр? Или вы загружаете все изображения сразу в OPencvStitcher?   -  person saurabheights    schedule 09.10.2016
comment
я могу получить матрицу гомографии, используя FindHomography между текущим кадром и кадром тритона. Я протестировал оба OPencv Sticher для всех ферм, хранящихся в векторе, и я проверил его, используя сопоставление с серфингом между текущим кадром и следующим кадром, а затем между результатом как предыдущего кадра, так и нового следующего кадра. Но результирующее изображение становится меньше, когда я использую сопоставление, а затем обертываю перспективу, а при использовании OpencvSticher та же самая деталь скрывалась.   -  person lafi raed    schedule 09.10.2016
comment
Есть 2 вещи, о которых я могу думать. 1 - Крайне маловероятно, но Opencv может уменьшить размер, чтобы сшивать быстрее, должен быть способ переопределить это. 2. Opencv не уменьшает их, но пиксели из предыдущего и следующего кадра сшиваются вместе и записываются поверх очень большого черного базового изображения (если исходное изображение 640x480, выходное изображение может быть 3200x2400).   -  person saurabheights    schedule 09.10.2016
comment
Кроме того: если вы можете получить матрицу преобразования (без особых дополнительных вычислений), вам нужно будет отслеживать матрицу преобразования, чтобы перейти от первого кадра к последнему кадру (обратное произведение матрицы множественного преобразования между каждым кадром t и t + 1). ). Умножьте матрицу преобразования с четырьмя углами размера изображения ({0,0}, {0,640}, {640,0}, {640,480}). Теперь вы можете получить ROI. Если это не решит проблему, предоставьте видео и пример кода для тестирования.   -  person saurabheights    schedule 09.10.2016
comment
Следите за матрицей преобразования: вы H01 для кадра 0 и фармеона, затем H12 для кадра один и кадр 2, затем H равно H01 * H12, затем я умножаю H на четыре угла. и что тогда я буду делать.??   -  person lafi raed    schedule 09.10.2016
comment
Да — обратите внимание, что это H10 и H21 для преобразования кадра 1 в кадр 0 и кадра 2 в кадр 1 соответственно.   -  person saurabheights    schedule 09.10.2016
comment
Хорошо, тогда что ты имеешь в виду, я получу Роя??? Пожалуйста, не могли бы вы ewplain больше   -  person lafi raed    schedule 09.10.2016
comment
Пожалуйста, опубликуйте небольшое видео и код, чтобы воспроизвести эту проблему. Нам обоим будет легче. Во-вторых, ROI — область интереса. Если вы знаете, где обрезать третье изображение, размещенное выше, вы можете отобразить только область интереса.   -  person saurabheights    schedule 09.10.2016
comment
я выложу код   -  person lafi raed    schedule 09.10.2016


Ответы (1)


Проблема: выходное изображение слишком велико.

Исходный код:-

int N = image1.rows + image2.rows;
int M = image1.cols+image2.cols;
warpPerspective(image1,result,H,cv::Size(N,M)); // Too big size.
cv::Mat half(result,cv::Rect(0,0,image2.rows,image2.cols));
result.copyTo(half);
namedWindow("Result",WINDOW_AUTOSIZE);
imshow( "Result", result);

Сгенерированное изображение результата хранит столько строк, сколько в изображении1 и в изображении2. Однако выходное изображение должно быть равно размерности изображения1, а изображение2 - размеру перекрывающейся области.

Еще одна проблема Почему вы искажаете изображение1. Вычислите H'(обратная матрица H) и деформируйте изображение2, используя H'. Вы должны зарегистрировать image2 на image1.

Кроме того, изучите, как работает warpPerspective. Он находит область ROI, в которую будет деформировано изображение2. Затем для каждого пикселя в этой области ROI результата (скажем, x, y) он находит соответствующее местоположение, скажем, (x', y') на изображении2. Примечание: (x', y') могут быть действительными значениями, например (4.5, 5.4).

Некоторая форма интерполяции (вероятно, линейная интерполяция) используется для нахождения значения пикселя для (x, y) в результате изображения.

Далее, как найти размер результирующей матрицы. Не используйте N, M. Используйте матрицу H' и деформируйте углы изображения, чтобы найти, где они заканчиваются.

Матрицу преобразования см. в этой вики и http://planning.cs.uiuc.edu/node99.html. Знайте разницу между матрицей вращения, поступательного, аффинного и перспективного преобразования. Затем прочитайте документы opencv здесь.

Вы также можете прочитать мой предыдущий ответ. Этот ответ показывает простую алгебру, чтобы найти площадь урожая. Вам нужно настроить код для четырех углов обоих изображений. Обратите внимание, что пиксели нового изображения также могут располагаться в отрицательных пикселях.

Пример кода (в java): -

import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

import org.opencv.calib3d.Calib3d;
import org.opencv.core.Core;
import org.opencv.core.CvType;
import org.opencv.core.DMatch;
import org.opencv.core.KeyPoint;
import org.opencv.core.Mat;
import org.opencv.core.MatOfDMatch;
import org.opencv.core.MatOfKeyPoint;
import org.opencv.core.MatOfPoint2f;
import org.opencv.core.Point;
import org.opencv.core.Scalar;
import org.opencv.core.Size;
import org.opencv.features2d.DescriptorExtractor;
import org.opencv.features2d.DescriptorMatcher;
import org.opencv.features2d.FeatureDetector;
import org.opencv.features2d.Features2d;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.imgproc.Imgproc;

public class Driver {

    public static void stitchImages() {
        // Read as grayscale
        Mat grayImage1 = Imgcodecs.imread("current_00000.bmp", 0);
        Mat grayImage2 = Imgcodecs.imread("current_00001.bmp", 0);

        if (grayImage1.dataAddr() == 0 || grayImage2.dataAddr() == 0) {
            System.out.println("Images read unsuccessful.");
            return;
        }

        // Create transformation matrix
        Mat transformMatrix = new Mat(3, 3, CvType.CV_32FC1);

        // -- Step 1: Detect the keypoints using AKAZE Detector
        int minHessian = 400;
        MatOfKeyPoint keypoints1 = new MatOfKeyPoint();
        MatOfKeyPoint keypoints2 = new MatOfKeyPoint();
        FeatureDetector surf = FeatureDetector.create(FeatureDetector.AKAZE);
        surf.detect(grayImage1, keypoints1);
        surf.detect(grayImage2, keypoints2);

        // -- Step 2: Calculate descriptors (feature vectors)
        DescriptorExtractor extractor = DescriptorExtractor.create(DescriptorExtractor.AKAZE);
        Mat descriptors1 = new Mat();
        Mat descriptors2 = new Mat();
        extractor.compute(grayImage1, keypoints1, descriptors1);
        extractor.compute(grayImage2, keypoints2, descriptors2);

        // -- Step 3: Match the keypoints
        DescriptorMatcher matcher = DescriptorMatcher.create(DescriptorMatcher.BRUTEFORCE);
        MatOfDMatch matches = new MatOfDMatch();
        matcher.match(descriptors1, descriptors2, matches);
        List<DMatch> myList = new LinkedList<>(matches.toList());

        // Filter good matches
        double min_dist = Double.MAX_VALUE;
        Iterator<DMatch> itr = myList.iterator();
        while (itr.hasNext()) {
            DMatch element = itr.next();
            min_dist = Math.min(element.distance, min_dist);
        }

        LinkedList<Point> img1GoodPointsList = new LinkedList<Point>();
        LinkedList<Point> img2GoodPointsList = new LinkedList<Point>();

        List<KeyPoint> keypoints1List = keypoints1.toList();
        List<KeyPoint> keypoints2List = keypoints2.toList();

        itr = myList.iterator();
        while (itr.hasNext()) {
            DMatch dMatch = itr.next();
            if (dMatch.distance >= 5 * min_dist) {
                img1GoodPointsList.addLast(keypoints1List.get(dMatch.queryIdx).pt);
                img2GoodPointsList.addLast(keypoints2List.get(dMatch.trainIdx).pt);
            } else {
                itr.remove();
            }
        }

        matches.fromList(myList);
        Mat outputMid = new Mat();
        System.out.println("best matches size: " + matches.size());
        Features2d.drawMatches(grayImage1, keypoints1, grayImage2, keypoints2, matches, outputMid);
        Imgcodecs.imwrite("outputMid - A - A.jpg", outputMid);

        MatOfPoint2f img1Locations = new MatOfPoint2f();
        img1Locations.fromList(img1GoodPointsList);

        MatOfPoint2f img2Locations = new MatOfPoint2f();
        img2Locations.fromList(img2GoodPointsList);

        // Find the Homography Matrix - Note img2Locations is give first to get
        // inverse directly.
        Mat hg = Calib3d.findHomography(img2Locations, img1Locations, Calib3d.RANSAC, 3);
        System.out.println("hg is: " + hg.dump());

        // Find the location of two corners to which Image2 will warp.
        Size img1Size = grayImage1.size();
        Size img2Size = grayImage2.size();
        System.out.println("Sizes are: " + img1Size + ", " + img2Size);

        // Store location x,y,z for 4 corners
        Mat img2Corners = new Mat(3, 4, CvType.CV_64FC1, new Scalar(0));
        Mat img2CornersWarped = new Mat(3, 4, CvType.CV_64FC1);

        img2Corners.put(0, 0, 0, img2Size.width, 0, img2Size.width);   // x
        img2Corners.put(1, 0, 0, 0, img2Size.height, img2Size.height); // y
        img2Corners.put(2, 0, 1, 1, 1, 1); // z - all 1

        System.out.println("Homography is \n" + hg.dump());
        System.out.println("Corners matrix is \n" + img2Corners.dump());
        Core.gemm(hg, img2Corners, 1, new Mat(), 0, img2CornersWarped);
        System.out.println("img2CornersWarped: " + img2CornersWarped.dump());

        // Find the new size to use
        int minX = 0, minY = 0; // The grayscale1 already has minimum location at 0
        int maxX = 1500, maxY = 1500; // The grayscale1 already has maximum location at 1500(possible 1499, but 1 pixel wont effect)
        double[] xCoordinates = new double[4];
        img2CornersWarped.get(0, 0, xCoordinates);
        double[] yCoordinates = new double[4];
        img2CornersWarped.get(1, 0, yCoordinates);
        for (int c = 0; c < 4; c++) {
            minX = Math.min((int)xCoordinates[c], minX);
            maxX = Math.max((int)xCoordinates[c], maxX);
            minY = Math.min((int)xCoordinates[c], minY);
            maxY = Math.max((int)xCoordinates[c], maxY);
        }
        int rows = (maxX - minX + 1);
        int cols = (maxY - minY + 1);

        // Warp to product final output
        Mat output1 = new Mat(new Size(cols, rows), CvType.CV_8U, new Scalar(0));
        Mat output2 = new Mat(new Size(cols, rows), CvType.CV_8U, new Scalar(0));
        Imgproc.warpPerspective(grayImage1, output1, Mat.eye(new Size(3, 3), CvType.CV_32F), new Size(cols, rows));
        Imgproc.warpPerspective(grayImage2, output2, hg, new Size(cols, rows));
        Mat output = new Mat(new Size(cols, rows), CvType.CV_8U);
        Core.addWeighted(output1, 0.5, output2, 0.5, 0, output);
        Imgcodecs.imwrite("output.jpg", output);
    }

    public static void main(String[] args) {
        System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
        stitchImages();
    }
}

Изменить дескриптор

Переместитесь в Akaze из Surf. Я видел идеальное совмещение изображений именно с этого.

Выходное изображение

Этот вывод занимает меньше места, а изменение дескриптора показывает идеальную регистрацию.

Выходное изображение

P.S.: ИМХО, программирование — это круто, но настоящее сокровище — это фундаментальные знания/понятия.

person saurabheights    schedule 09.10.2016
comment
Я не могу сразу. В настоящее время на Mac и Xcode выдает какую-то неизвестную ошибку. Кроме того, вы не предоставили видео, только несколько кадров. Кроме того, в вашем коде используется файл current_00000.bmp, которого нет в указанной вами папке GoogleDrive. - person saurabheights; 09.10.2016
comment
вот ссылка на другой диск dropbox.com/sh/270yb3heeqjv8sk/AADqtRceyjKmwkY1uSP5Iu5ka?dl= 0 у меня только кадры, у меня нет оригинального видео - person lafi raed; 10.10.2016
comment
@lafiraed: я не смогу быстро предоставить код. Весь ccode дает кучу ошибок на Mac OSX 10.12. - person saurabheights; 10.10.2016
comment
хорошо, но, пожалуйста, измените путь к своим изображениям, он не такой, как у меня. Спасибо - person lafi raed; 10.10.2016
comment
@lafiraed: Есть одна проблема. Можете ли вы деформировать изображение 1 в выход 1 и скопировать изображение 2 в выход 2, а затем использовать addWeighted (выход 1, 0,5, вывод 2, 0,5, 0, вывод), чтобы увидеть, идеально ли искаженное изображение 1 соответствует изображению 2. - person saurabheights; 12.10.2016
comment
поэтому я переношу изображение 1 для вывода с помощью H или H'?? - person lafi raed; 12.10.2016
comment
также output1 и output2 просто должны быть матом, верно?? - person lafi raed; 12.10.2016
comment
вот результат выходного изображения drive.google.com/file/ д/0B1D_FX2T2QR7RTl0MmlLOU1abzQ/ - person lafi raed; 12.10.2016
comment
И вот код Mat H = findHomography( obj, scene, CV_RANSAC ); инвертировать (Ч, ч); // Используйте матрицу гомографии для деформации изображений cv::Mat result,output1,output2,output; warpPerspective(image1,output1,H,cv::Size(1500,1500)); изображение2.copyTo(выход2); addWeighted(выход1, 0,5, вывод2, 0,5, 0, вывод); - person lafi raed; 12.10.2016
comment
Ваше зарегистрированное изображение выглядит хорошо. Поскольку у меня нет Surf (запатентованного) в opencv 3, пришлось перейти на Orb, что не дало хорошего результата совмещения изображений. - person saurabheights; 12.10.2016
comment
@lafiraed: проверьте обновленный ответ. Сначала перейдите от Surf к Akaze и посмотрите на улучшение изображения, которое вы загрузили несколько часов назад. Затем внесите соответствующие изменения для уменьшения размера выходного изображения. Кроме того, opencv и C++ поссорились на моем Mac, поэтому я поговорил с java, который является хорошим другом opencv, и попросил его решить эту проблему. Таким образом, пример кода находится в java. Кроме того, проверьте ссылку на вики, которую я добавил из истории редактирования ответа. - person saurabheights; 12.10.2016
comment
я конвертирую его в c++, я дам вам знать результат - person lafi raed; 12.10.2016
comment
Возможно, стоит/интересно изучить другие реализации SURF (например, Pan-o-Matic, если вам нравится C++ или BoofCV в Java) и сравнить их с AKAZE. Немного назад я провел исследование производительности нескольких реализаций. OpenCV был примерно на 18% хуже эталона по стабильности дескриптора. Еще тогда написал отчет об ошибке. Веб-сайт. - person lessthanoptimal; 15.01.2017
comment
@PeterAbeles: Спасибо за информативную ссылку. Пожалуйста, не могли бы вы предоставить больше информации о заполненной ошибке (я думаю, вы имели в виду заполненную ошибку в OpenCV)? Его ссылки должно быть достаточно. Еще раз моя искренняя благодарность. - person saurabheights; 15.01.2017
comment
@saurabheights отправил отчет об ошибке, это было бы более ясным способом сказать это. Пытался найти сам отчет об ошибке, но вместо этого нашел разговор с сопровождающим OpenCV. Одна из проблем, которую я обнаружил, заключалась в том, что это был пол, а не округление, но у меня были проблемы с быстрым отслеживанием других проблем. Я также вижу, что я никогда не отправлял патч, как обещал, по крайней мере, для одной проблемы, которую я нашел :p - person lessthanoptimal; 16.01.2017
comment
Найден отчет об ошибке. Это из-за ошибки, появившейся в 2.4, которая ухудшила производительность. Я мог бы использовать результаты 2.3, которые были лучше, если предположить, что кто-то это исправит. - person lessthanoptimal; 16.01.2017
comment
@PeterAbeles: Surf/SIFT — одна из моих самых любимых техник, а ваша работа — настоящее сокровище для меня. Большое спасибо. Мне потребуется несколько дней, чтобы просмотреть вашу статью, исправления ошибок OpenCV, но это будет хорошим уроком. :) - person saurabheights; 16.01.2017