Перейти к содержимому
Compvision.ru
RinOS

Стерео зрение

Recommended Posts

Всем привет! Решил заняться стерео зрением.

Нашел в сети такой проект на OpenCV: http://code.google.com/p/opencvstereovision/

даже удалось скомпилить его) exe во вложении.

Поигрался с программой пытался калибровать шахматной доской но результат мягко говоря неочень...

Раньше ни когда этим не занимался сразу возникло несколько вопросов.

Какое расстояние должно быть между камерами?

a699d7ed427e.png

Как они должны быть направлены? (какой угол примерно)

3d875203edef.png

(ехе требует библиотеки OpenCV 1.1 и библиотеки Qt если в сети не найдете выложу )

OpenCV-Qt-StereoCalibration.rar

  • Like 2

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Присоединяюсь к вопросу.

  • Like 1

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Читая статьи на эту тему и форумы выяснил что

1. Оси камер должны быть параллельны

2. Расстояние между камерами 10-15 см.

3. Калибровка очень, и очень важна (правда ни чего не нашел о том как правильно калибровать...)

К примеру беру шахматную доску, пытаюсь калибровать... и каждый раз разный результат...

Кто знает как правильно калибровать)?

Доска для калибровки во вложении.

ChessBoard Stereo Calibration.pdf

  • Like 1

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Допустим как то я как то скалибровал и получил disparityImage, карта глубины конечно не ахти, впрочем сойдет...

Карта глубины как я понял это не реальные числа глубины, а разности между слоями... (так ли)?

И что бы получить расстояние до объекта, надо преобразовать координаты.

Если я правильно понял то для этого используется функция cvReprojectImageTo3D() (так ли)?

В которой:

первый параметр disparityImage который нам создал cvFindStereoCorrespondenceBM()

второй параметр IplImage (size, IPL_DEPTH_32F, 3)

третий параметр самый интересный... матрица СvMat(4,4, CV_64F), в доке написано что:

(You’re going to need the reprojection matrix (named Q) from the cvStereoRectify method.)

Дело в том что в этой программе, используется cvStereoRectifyUncalibrated, а там ни какого Q, нет...

откуда бы взять эту матрицу Q?

И вообще правильный ли ход мыслей, для получения реальных координат, из disparityImage?

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Здравствуйте, RinOS.

Рекомендую все-таки использовать cvStereoRectify() т.к. результат его работы более точен, а cvStereoRectifyUncalibrated() лучше использовать когда существуют значительные непараллельности в оптических осях камер, насколько я понимаю.

Критерием правильности калибровки стерео может служить правильное число в векторе T (который означает расстояние между оптическими осями по оси x, y и z) камер. У меня, при расстоянии между камерами 12 см, и 29 калибровочных пар изображений 640х480 в оттенках серого (изображения я сохраняю предварительно в bmp, чтобы каждый раз не мучаться с их показом камерам) величина составляет: цитирую xml содержимое <data>-1.1886876922892217e-001 -7.8263643755714435e-004 -4.6620003758508491e-003</data>, (все величины в метрах - первая величина - это сдвиг по оси X, то есть расстояние между камерами). То есть 1.6 %, что может быть точнее измерянного мною расстояния. Чем шире расстояние между камерами, тем лучше будет восприятие на более дальних расстояниях, и тем хуже будет поле зрения камеры, при обзоре близких предметов.

Для того чтобы величина вектора T содержала метрические величины, необходимо, чтобы вы правильно (в метрических единицах) указали размер клеточки при калибровке. В книге learning OpenCV, в примере стерео (стр 445), есть константа squareSize, у меня в коде примерно так (размер клетки 3 см):


float chesbSquareSize = 0.030f; // 30 mm is a Square size


for (size_t pair_idx = 0; pair_idx < boards_count; pair_idx++)

{

	for (size_t i=pair_idx*board_n,j = 0;j < board_n; ++i,++j)

	{

		// Chessboard points

		CV_MAT_ELEM(*image_pointsL,float,i,0) = vCalibData[IMG_LEFT][pair_idx].vPoints[j].x;

		CV_MAT_ELEM(*image_pointsL,float,i,1) = vCalibData[IMG_LEFT][pair_idx].vPoints[j].y;


		CV_MAT_ELEM(*image_pointsR,float,i,0) = vCalibData[IMG_RIGHT][pair_idx].vPoints[j].x;

		CV_MAT_ELEM(*image_pointsR,float,i,1) = vCalibData[IMG_RIGHT][pair_idx].vPoints[j].y;


		// Linear space remapping points

		CV_MAT_ELEM(*object_points,float,i,0) = (float)(j/w) * chesbSquareSize;

		CV_MAT_ELEM(*object_points,float,i,1) = (float)(j%w) * chesbSquareSize;

		CV_MAT_ELEM(*object_points,float,i,2) = 0;

	};


	CV_MAT_ELEM(*point_counts,UINT,pair_idx,0) = (UINT)board_n;

};

Откалиброванные матрицы удобно сохранять в xml. Для того, чтобы калибровка была корректной, рекомендуют использовать "шахматную доску" с не одинаковым кол-вом клеток по ширине и высоте (я заметил вы такую и использовали). Для улучшения качества BlockMatcher'а, попробуйте BMState->uniquenessRatio = 0; Привожу мои рабочие параметры:

BMState->preFilterSize = 17;

BMState->preFilterCap = 31;

BMState->SADWindowSize = 11;

BMState->minDisparity = 13;

BMState->numberOfDisparities = 256;

BMState->textureThreshold = 35;

BMState->uniquenessRatio = 0;

Искомую матрицу репроекции Q вы можете заполнить вручную по формуле Q= стр 435 Learn OpenCV, используя ранее вычисленные матрицы: CameraMatrix и T, например.. но мне кажется это сложный путь, и ... некоторые величины (f, n) придется выдумывать.

Вот набросал матрицу, которую вы можете использовать, подставив лишь расстояние между объективами камер:

Q_matrix_try.PNG

Но я не уверен, что она даст для вас точный и качественный результат. f можно уменьшить вовсе до 5 метров.

Если вы вызовете cvStereoRectify() то наверняка ничего не потеряете, по книге инных вариантов не приводится. Матрицу Q нельзя извлечь из cvStereoRectifyUncalibrated() поскольку (стр 431):

The advantage of Hartley’s algorithm is that online stereo calibration can be performed

simply by observing points in the scene. The disadvantage is that we have no sense of

image scale. For example, if we used a chessboard for generating point matches then

we would not be able to tell if the chessboard were 100 meters on each side and far away

or 100 centimeters on each side and nearby. Neither do we explicitly learn the intrinsic

camera matrix, without which the cameras might have dif erent focal lengths, skewed

pixels, dif erent centers of projection, and/or dif erent principal points. As a result, we

can determine 3D object reconstruction only up to a projective transform. What this

means is that dif erent scales or projections of an object can appear the same to us (i.e.,

the feature points have the same 2D coordinates even though the 3D objects differ).

То есть, теоретически, если вы при калибровке, задавали реальные метрические координаты (с помощью размера) ваших клеточек, то на выходе ReprojectTo3D проекции получите реальные размеры и расстояния до объекта в метрах.

Насколько я понимаю, disparityImage должен быть Float(существует быстрый вариант с Int). Подавать нужно disparityImage не нормализованный.

Репроекцией еще сам не занимался, поэтому грабли раскрыть в этой теме пока не могу (может на этих выходных, тогда я бы отписался здесь).

Прикрепляю свою шахматную доску (visio), может пригодится.

  • Like 3

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

При подаче в функцию cvInitUndistortRectifyMap (вы же ректифицируете изображение?), четвертый параметр - матрица NewCameraMatrix для правой камеры, т.е. "M2". Она содержит все недостающие величины. Я так понимаю, в вашем примере, эта матрица тоже рассчитывается подобно тому как в книге (в коде на примере) стр 450 внизу (после "OR ELSE HARTLEY'S METHOD, ... , compute the rectification transformation directly ... ").

M2_LaTeX.gif

Вот как выглядит матрица Q у меня. (моя предыдущая теоретическая имела C1 и C2 равные 0).

MatQ_LaTeX.gif

У меня f = 5.84*10^2, C1 = 3.11*10^2, C2 = 2.41*10^2, Tx = 0.118

Вот написал функцию, которая принимает матрицу 4 параметра cvInitUndistortRectifyMap и расстояние между камерами в метрах, и возвращает double_1channel матрицу Q (возвращаемая ею матрица у меня полностью совпала с откалиброванной Q из StereoCalibrate() по выводу в xml).


// vM2_right - та же что и 4 параметр cvInitUndistortRectifyMap для правой камеры

// т.е. new_camera_matrix правой камеры

CvMat * CreateArtificialMatQ(CvMat *vM2_right, double Tx)

{

	// находим нужные величины из матрицы правой камеры vM2

	double f = CV_MAT_ELEM(*vM2_right,double,0,0);

	double C1 = CV_MAT_ELEM(*vM2_right,double,0,2);

	double C2 = CV_MAT_ELEM(*vM2_right,double,1,2);


	// заполняем правильно элементы матрицы

	CvMat *MatQ_artificial = cvCreateMat(4,4,CV_64FC1);

	cvZero(MatQ_artificial);

	CV_MAT_ELEM(*MatQ_artificial,double,0,0) = 1;

	CV_MAT_ELEM(*MatQ_artificial,double,1,1) = 1;


	CV_MAT_ELEM(*MatQ_artificial,double,0,3) = -C1;

	CV_MAT_ELEM(*MatQ_artificial,double,1,3) = -C2;

	CV_MAT_ELEM(*MatQ_artificial,double,2,3) = f;

	CV_MAT_ELEM(*MatQ_artificial,double,3,2) = -1.0/Tx;


	return MatQ_artificial;

}

Но я не могу пока-что проверить будет ли она правильно работать на cvStereoRectifyUncalibrated.

  • Like 1

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах
Рекомендую все-таки использовать cvStereoRectify() т.к. результат его работы более точен, а cvStereoRectifyUncalibrated() лучше использовать когда существуют значительные непараллельности в оптических осях камер, насколько я понимаю.

Смотрел пример stereo_calib.cpp, там показаны оба метода cvStereoRectify и cvStereoRectifyUncalibrated, мне показалось что cvStereoRectifyUncalibrated, работает лучше... по крайней мере карта высот выглядит правдоподобнее.

Искомую матрицу репроекции Q вы можете заполнить вручную по формуле Q= стр 435 Learn OpenCV, используя ранее вычисленные матрицы: CameraMatrix и T, например.. но мне кажется это сложный путь, и ... некоторые величины (f, n) придется выдумывать.

У меня есть модуль (см. вложение) который вроде как строит Q, по некоторым параметрам:

* \brief Constructor that takes camera parameters and parameterizes the object.

* The object is ready to be used after constructed by this constructor.

* @param Fx - focal length in x direction of the rectified image in pixels.

* @param Fy - focal length in y direction of the rectified image in pixels.

* @param Tx - Translation in x direction from the left camera to the right camera.

* @param Clx - x coordinate of the optical center of the left camera

* @param Crx - x coordinate of the optical center of the right camera

* @param Cy - y coordinate of the optical center of both left and right camera

* @param dispUnitScale - the unit of the value in a disparity map. e.g. 1/4 of a pixel.

*/

Правда не известно откуда брать точные значения этих параметров...

При подаче в функцию cvInitUndistortRectifyMap (вы же ректифицируете изображение?)

Да, вы можете посмотреть исходник с которым я работаю, ссылка в первом посте.

Вот написал функцию, которая принимает матрицу 4 параметра cvInitUndistortRectifyMap

Я попробовал использовать ее вот что получилось:

Q возвращенное функцией cvStereoRectify:

[ 1., 0., 0., -179.1385765075683600, 0., 1., 0.,

-120.1025462150573700, 0., 0., 0., 350.7473711626223000, 0., 0.,

-24.8326591292256540, -538.7927019658170600 ]

Q полученное с помощью cvStereoRectifyUncalibrated и вашей функции:

[ 1., 0., 0., -150.7913341775544300, 0., 1., 0.,

-102.6185674775258400, 0., 0., 0., 352.8618670197555500, 0., 0.,

-25.9740261147689360, 0. ]

Пытался много раз калибровать, Q иногда больше похожи иногда меньше в среднем как-то так.

stereo_utils.rar

  • Like 2

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Вызывал я функцию cvReprojectImageTo3D с разным Q, и пришел к выводу что она все-таки работает (карта высот довольно реалистичная в MatLab-e).

a6a50d5e9f4f.jpg

484140c9f0e6.jpg

f2f0e141cc98.jpg

17a500600d6d.jpg

Функция cvReprojectImageTo3D, возвращает 3х канальное изображение 3 канал, глубина - расстояние до объекта.

Так я получаю расстояние


  cvReprojectImageTo3D(imageDepth, 3dImage, Q);

  cvSetImageCOI(3dImage, 3);

  cvCopy(3dImage, Zimg, 0);


  dist = cvGetReal2D(Zimg, 160, 120);

Но вот в чем прикол... Если объект дальше то и значение dist должно быть больше, а происходит ровно наоборот...

Чем ближе объект, тем больше значение dist.

К примеру замерял расстояние от камеры до себя, вот что получил:

расстояние 60 см, dist равен 0,117

расстояние 70 см, dist равен 0,093

расстояние 80 см, dist равен 0,079

расстояние 90 см, dist равен 0,661

расстояние 100 см, dist равен 0,580

Где то не вяжется... Или я чего то не понимаю...

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

На одном китайском сайте, нашел пример вывода в 3d, disparity image :)

Для лучшего эффекта предварительно нормализуем изображение


imageDepthNormalized = cvCreateMat( imageSize.height,imageSize.width, CV_8U );

cvScale(imageDepth, imageDepthNormalized, 1, 256);

8ea589a7a5f1.png

Программа, исходник и картинка во вложении, картинку берет из "c:\z.bmp"

glut3d.rar

  • Like 2

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах
Если объект дальше то и значение dist должно быть больше, а происходит ровно наоборот...Чем ближе объект, тем больше значение dist.

Тоже не могу получить реальные координаты. Модуль значения disp растет в соответствии с дистанцией, но не адекватно.

Z=fT/d, T-расстояние между камерами, d - диспаритет, значит ошибка в f?

post-461-0-47138500-1305458689_thumb.jpg

  • Like 1

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Ввел в формулу поправочный коэффициент a, который вычислил экспериментально: z=afT/d. Теперь дистанция определяется верно, но в чем проблема так и не понял. Скрепя сердцем оставил пока этот костыль.

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Подниму старую тему но все же, пытаюсь реализовать код представленный в посте конкретно:

cvReprojectImageTo3D(imageDepth, 3dImage, Q);

  cvSetImageCOI(3dImage, 3);

  cvCopy(3dImage, Zimg, 0);


  dist = cvGetReal2D(Zimg, 160, 120);

opencv ругается что GetReal пожддерживает только однокональные массивы, а теперь вопрос если 3dImage трехканальное то как оно использованно в CvGetReal?

и еще не сочтите за наглость откуда взяты данные для графиков представленных в посте?

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Подниму старую тему но все же, пытаюсь реализовать код представленный в посте конкретно:

cvReprojectImageTo3D(imageDepth, 3dImage, Q);

  cvSetImageCOI(3dImage, 3);

  cvCopy(3dImage, Zimg, 0);


  dist = cvGetReal2D(Zimg, 160, 120);
opencv ругается что GetReal пожддерживает только однокональные массивы, а теперь вопрос если 3dImage трехканальное то как оно использованно в CvGetReal? и еще не сочтите за наглость откуда взяты данные для графиков представленных в посте?
Никогда не пользовался С-интерфейсом, но приведу часть кода на С++ из моего проекта

pcl::PointCloud<pcl::PointXYZRGB>::Ptr

    BaseStereoMatcher::pointCloud(const cv::Mat &disp, const cv::Mat &image, const CalibrationParams &calib_params)

{

    Q_ASSERT((image.rows != 0) && (image.cols != 0) && (image.rows == disp.rows) && (image.cols == disp.cols) && (image.type() == CV_8UC3));


    pcl::PointCloud<pcl::PointXYZRGB>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZRGB>);


    cv::Matx44f Q = calib_params.DisparityToDepthMatrix;

    cv::Matx41f reproj_point;

    for (int v = 0; v < disp.rows; v++)

    {

        for (int u = 0; u < disp.cols; u++)

        {

            float d;

            if (disp.type() == CV_16S)

                d = disp.at<short>(v, u) / 16.0f;

            if (disp.type() == CV_32F)

                d = disp.at<float>(v, u);

            if (d <= FLT_EPSILON)

            {

               // point.x = point.y = point.z = bad_point;

                continue;

            }

            pcl::PointXYZRGB point;


            cv::Matx41f uv_point(u,v,d,1); //диспарантность в однородных координатах

            reproj_point = Q * uv_point;   //так осуществляется проекция в однородные 3д координаты


            float w = reproj_point.val[3];

            point.x = -reproj_point.val[0] / w;

            point.y = -reproj_point.val[1] / w;

            point.z = reproj_point.val[2] / w; 

// point - готовая точка в 3д


            cv::Vec3b pix = image.at<cv::Vec3b>(v, u);

            uint8_t r = pix[2], g = pix[1], b = pix[0];

            uint32_t rgb = (static_cast<uint32_t>(r) << 16 | static_cast<uint32_t>(g) << 8 | static_cast<uint32_t>();

            point.rgb = *reinterpret_cast<float*>(&rgb);

            cloud->push_back(point);

        }

    }

    return cloud;

}

[/code]

Тоже самое выполняется в функции ReprojectTo3D (откомментированные строчки), так что можешь врунчую заполнить матрицу с 3д координатами.

И, кстати, в матрице диспарантности все ее величины умножены на 16, поэтому прежде чем ее использовать, нужно значение диспарантности поделить на 16, выше в коде ты можешь это увидеть.

  • Like 2

Поделиться сообщением


Ссылка на сообщение
Поделиться на других сайтах

Создайте учётную запись или войдите для комментирования

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

Создать учётную запись

Зарегистрируйтесь для создания учётной записи. Это просто!

Зарегистрировать учётную запись

Войти

Уже зарегистрированы? Войдите здесь.

Войти сейчас


  • Сейчас на странице   0 пользователей

    Нет пользователей, просматривающих эту страницу

×