2010-03-19 3 views
48

BOUNTY STATUS UPDATE:коррекции искажений фишай программно

I discovered how to map a linear lens, от destination координат source координат.

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

  • 1). Я действительно пытаюсь перевернуть его и сопоставить исходные координаты с координатами назначения. Что такое обратное, в коде в стиле преобразованных функций, которые я разместил?

  • 2). Я также вижу, что мое искажение несовершенно на некоторых объективах - предположительно те, которые не являются строго линейными. Какова эквивалентность координат источника и места назначения для этих линз? Опять же, больше коды, чем просто математические формулы, пожалуйста ...


Question as originally stated:

У меня есть некоторые моменты, которые описывают позиции на снимке, выполненную с объективом.

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

Я нашел this description, как создать эффект рыбий глаз, но не как его изменить.

Существует также blog post, в котором описано, как использовать инструменты для этого; эти картины от:

(1): SOURCEOriginal photo link

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

(2): DESTINATIONOriginal photo link

Выход: исправленное изображение (технически также с перспективной коррекции, но это отдельный шаг).

Как вы рассчитываете радиальное расстояние от центра, чтобы перейти от рыбий глаз к прямолинейному?

Моей функция окурка выглядит следующим образом:

Point correct_fisheye(const Point& p,const Size& img) { 
    // to polar 
    const Point centre = {img.width/2,img.height/2}; 
    const Point rel = {p.x-centre.x,p.y-centre.y}; 
    const double theta = atan2(rel.y,rel.x); 
    double R = sqrt((rel.x*rel.x)+(rel.y*rel.y)); 
    // fisheye undistortion in here please 
    //... change R ... 
    // back to rectangular 
    const Point ret = Point(centre.x+R*cos(theta),centre.y+R*sin(theta)); 
    fprintf(stderr,"(%d,%d) in (%d,%d) = %f,%f = (%d,%d)\n",p.x,p.y,img.width,img.height,theta,R,ret.x,ret.y); 
    return ret; 
} 

С другой стороны, я мог какое-то образом преобразовать изображение из Рыбьего глаза к прямолинейному, прежде чем найти точки, но я полностью одурманен OpenCV documentation. Есть ли простой способ сделать это в OpenCV, и он работает достаточно хорошо, чтобы сделать это с живой видео-лентой?

+0

Я не совсем понимаю, что вы находясь в поиске. Карты рыбий глаз от сферы к плоскости изображения. Обратное отображение будет от изображения обратно к сфере справа? Какую прямолинейную координату вы ищете? – mtrw

+0

@mtrw Мое исходное изображение искажено fisheye, и я хочу его недооценить – Will

+0

Так выглядит ли картинка на http://photo.net/learn/fisheye/, что вы ищете? – mtrw

ответ

29

description you mention утверждает, что проекция на пин-отверстие камеры (один, который не вносит искажений объектива) моделируется

R_u = f*tan(theta) 

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

R_d = 2*f*sin(theta/2) 

вы уже знаете r_d и тета, и если вы знали, фокусное расстояние камеры (представленное F), то коррекции изображения составит вычисления R_U с точки зрения r_d и тета. Иными словами,

R_u = f*tan(2*asin(R_d/(2*f))) 

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

Чтобы решить ту же проблему, используя OpenCV, вам необходимо получить внутренние параметры камеры и коэффициенты искажения объектива. См., Например, главу 11 из Learning OpenCV (не забудьте проверить correction). Затем вы можете использовать такие программы, как этот (написано с привязками Python для OpenCV) для того, чтобы обратить вспять искажение объектива:

#!/usr/bin/python 

# ./undistort 0_0000.jpg 1367.451167 1367.451167 0 0 -0.246065 0.193617 -0.002004 -0.002056 

import sys 
import cv 

def main(argv): 
    if len(argv) < 10: 
    print 'Usage: %s input-file fx fy cx cy k1 k2 p1 p2 output-file' % argv[0] 
    sys.exit(-1) 

    src = argv[1] 
    fx, fy, cx, cy, k1, k2, p1, p2, output = argv[2:] 

    intrinsics = cv.CreateMat(3, 3, cv.CV_64FC1) 
    cv.Zero(intrinsics) 
    intrinsics[0, 0] = float(fx) 
    intrinsics[1, 1] = float(fy) 
    intrinsics[2, 2] = 1.0 
    intrinsics[0, 2] = float(cx) 
    intrinsics[1, 2] = float(cy) 

    dist_coeffs = cv.CreateMat(1, 4, cv.CV_64FC1) 
    cv.Zero(dist_coeffs) 
    dist_coeffs[0, 0] = float(k1) 
    dist_coeffs[0, 1] = float(k2) 
    dist_coeffs[0, 2] = float(p1) 
    dist_coeffs[0, 3] = float(p2) 

    src = cv.LoadImage(src) 
    dst = cv.CreateImage(cv.GetSize(src), src.depth, src.nChannels) 
    mapx = cv.CreateImage(cv.GetSize(src), cv.IPL_DEPTH_32F, 1) 
    mapy = cv.CreateImage(cv.GetSize(src), cv.IPL_DEPTH_32F, 1) 
    cv.InitUndistortMap(intrinsics, dist_coeffs, mapx, mapy) 
    cv.Remap(src, dst, mapx, mapy, cv.CV_INTER_LINEAR + cv.CV_WARP_FILL_OUTLIERS, cv.ScalarAll(0)) 
    # cv.Undistort2(src, dst, intrinsics, dist_coeffs) 

    cv.SaveImage(output, dst) 


if __name__ == '__main__': 
    main(sys.argv) 

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

+0

Это зависит от наличия доступа к соответствующей камере. Я этого не делаю, я просто получаю видео. Кроме того, мне кажется, что камеры серийно выпускаются и не могут быть много различий индивидуально, конечно? Инструменты, связанные в оригинальной статье, не требуют, чтобы кто-то стоял перед камерой с шахматной доской !? – Will

+2

Параметры камеры варьируются в пределах одной камеры, просто изменяя масштаб. Кроме того, вы можете использовать методы автоматической калибровки вместо использования шахматной доски. В любом случае, я отредактировал свой ответ, чтобы ответить на первую часть вашего вопроса, предоставив вам формулу, которую вы искали. – jmbr

+0

Спасибо, jmbr! Мир начинает иметь какой-то смысл. Я не могу получить R_u = f * tan (2 * asin (R_d/(2 * f))), чтобы дать мне что-нибудь, кроме NaN. Я не знаю тригонометрии и ее того, что удерживает меня прямо сейчас :( – Will

3

Если вы считаете, что ваши формулы являются точными, вы можете вычи точную формулу с тригонометрии, например, так:

Rin = 2 f sin(w/2) -> sin(w/2)= Rin/2f 
Rout= f tan(w)  -> tan(w)= Rout/f 

(Rin/2f)^2 = [sin(w/2)]^2 = (1 - cos(w))/2 -> cos(w) = 1 - 2(Rin/2f)^2 
(Rout/f)^2 = [tan(w)]^2 = 1/[cos(w)]^2 - 1 

-> (Rout/f)^2 = 1/(1-2[Rin/2f]^2)^2 - 1 

Однако, как @jmbr говорит, фактическое искажение камеры будет зависеть от объектива и зума , Вместо того, чтобы полагаться на фиксированной формуле, вы можете попробовать полиномиальное разложение:

Rout = Rin*(1 + A*Rin^2 + B*Rin^4 + ...) 

щипая сначала, то коэффициенты высшего порядка, можно вычислить любую разумную локальную функцию (в виде расширения использует симметрии задачи). В частности, должно быть возможно вычислить начальные коэффициенты, чтобы аппроксимировать теоретическую функцию выше.

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

Edit: согласно вашему запросу, эквивалентный коэффициент масштабирования для указанной выше формуле:

(Rout/f)^2 = 1/(1-2[Rin/2f]^2)^2 - 1 
-> Rout/f = [Rin/f] * sqrt(1-[Rin/f]^2/4)/(1-[Rin/f]^2/2) 

Если построить вышеуказанную формулу наряду с загаром (вх/ж), вы можете увидеть, что они очень похожи форма. В основном, искажение касательной становится серьезным до того, как sin (w) сильно отличается от w.

обратная формула должна быть что-то вроде:

Rin/f = [Rout/f]/sqrt(sqrt(([Rout/f]^2+1) * (sqrt([Rout/f]^2+1) + 1)/2) 
7

(Оригинальный плакат, обеспечивая альтернативу)

Следующая функция отображает назначения (прямолинейные) координаты источника (Fisheye-искажается) координаты. (я оценил бы помощь в движении задним ходом его)

я получил в этот момент путем проб и ошибок: я принципиально не понять, почему этот код работает, объяснения и повышение точности оценили!

def dist(x,y): 
    return sqrt(x*x+y*y) 

def correct_fisheye(src_size,dest_size,dx,dy,factor): 
    """ returns a tuple of source coordinates (sx,sy) 
     (note: values can be out of range)""" 
    # convert dx,dy to relative coordinates 
    rx, ry = dx-(dest_size[0]/2), dy-(dest_size[1]/2) 
    # calc theta 
    r = dist(rx,ry)/(dist(src_size[0],src_size[1])/factor) 
    if 0==r: 
     theta = 1.0 
    else: 
     theta = atan(r)/r 
    # back to absolute coordinates 
    sx, sy = (src_size[0]/2)+theta*rx, (src_size[1]/2)+theta*ry 
    # done 
    return (int(round(sx)),int(round(sy))) 

При использовании с коэффициентом 3,0, успешно undistorts изображения, используемые в качестве примеров (я не делал попытку качества интерполяции):

Мертвая ссылка

(И это от сообщение в блоге, для сравнения :)

Using Panotools

+0

Причина, по которой ваш код работает, заключается в том, что вы масштабируете (rx, ry) коэффициентом theta (который теперь является отношением, а не углом). Если исходный объектив имеет (как В статье wiki говорится о «линейном сопоставлении» от угла обзора до смещения изображения, я считаю, что atan (r)/r верен. – comingstorm

+1

Реверс вашего отображения должен быть масштабирован на коэффициент tan (r ')/r' , где r '- радиус от центра неискаженного изображения. – comingstorm

+1

И причина, по которой обе эти работы заключаются в том, что если у вас есть вектор v' = v * k (| v |), и вы хотите | v '| = f (| v |), вы принимаете абсолютное значение первого уравнения: | v '| = | v | * k (| v |) = f (| v |) - так, что k (| v |) = F (| v |)/| v | – comingstorm

2

Я слепо осущ Выбрав формулы из here, я не могу гарантировать, что он сделает то, что вам нужно.

Используйте auto_zoom, чтобы получить значение для параметра zoom.


def dist(x,y): 
    return sqrt(x*x+y*y) 

def fisheye_to_rectilinear(src_size,dest_size,sx,sy,crop_factor,zoom): 
    """ returns a tuple of dest coordinates (dx,dy) 
     (note: values can be out of range) 
crop_factor is ratio of sphere diameter to diagonal of the source image""" 
    # convert sx,sy to relative coordinates 
    rx, ry = sx-(src_size[0]/2), sy-(src_size[1]/2) 
    r = dist(rx,ry) 

    # focal distance = radius of the sphere 
    pi = 3.1415926535 
    f = dist(src_size[0],src_size[1])*factor/pi 

    # calc theta 1) linear mapping (older Nikon) 
    theta = r/f 

    # calc theta 2) nonlinear mapping 
    # theta = asin (r/(2 * f)) * 2 

    # calc new radius 
    nr = tan(theta) * zoom 

    # back to absolute coordinates 
    dx, dy = (dest_size[0]/2)+rx/r*nr, (dest_size[1]/2)+ry/r*nr 
    # done 
    return (int(round(dx)),int(round(dy))) 


def fisheye_auto_zoom(src_size,dest_size,crop_factor): 
    """ calculate zoom such that left edge of source image matches left edge of dest image """ 
    # Try to see what happens with zoom=1 
    dx, dy = fisheye_to_rectilinear(src_size, dest_size, 0, src_size[1]/2, crop_factor, 1) 

    # Calculate zoom so the result is what we wanted 
    obtained_r = dest_size[0]/2 - dx 
    required_r = dest_size[0]/2 
    zoom = required_r/obtained_r 
    return zoom 
3

Я нашел этот PDF-файл, и я доказал, что математика правильно (для линии vd = *xd**fv+v0 which should say vd = **yd**+fv+v0 за исключением).

http://perception.inrialpes.fr/CAVA_Dataset/Site/files/Calibration_OpenCV.pdf

Он не использует все последние со-efficients, что OpenCV имеет в наличии, но я уверен, что она может быть адаптирована довольно легко.

double k1 = cameraIntrinsic.distortion[0]; 
double k2 = cameraIntrinsic.distortion[1]; 
double p1 = cameraIntrinsic.distortion[2]; 
double p2 = cameraIntrinsic.distortion[3]; 
double k3 = cameraIntrinsic.distortion[4]; 
double fu = cameraIntrinsic.focalLength[0]; 
double fv = cameraIntrinsic.focalLength[1]; 
double u0 = cameraIntrinsic.principalPoint[0]; 
double v0 = cameraIntrinsic.principalPoint[1]; 
double u, v; 


u = thisPoint->x; // the undistorted point 
v = thisPoint->y; 
double x = (u - u0)/fu; 
double y = (v - v0)/fv; 

double r2 = (x*x) + (y*y); 
double r4 = r2*r2; 

double cDist = 1 + (k1*r2) + (k2*r4); 
double xr = x*cDist; 
double yr = y*cDist; 

double a1 = 2*x*y; 
double a2 = r2 + (2*(x*x)); 
double a3 = r2 + (2*(y*y)); 

double dx = (a1*p1) + (a2*p2); 
double dy = (a3*p1) + (a1*p2); 

double xd = xr + dx; 
double yd = yr + dy; 

double ud = (xd*fu) + u0; 
double vd = (yd*fv) + v0; 

thisPoint->x = ud; // the distorted point 
thisPoint->y = vd; 
3

Я взял то, что сделал JMBR, и в основном изменил его. Он взял радиус искаженного изображения (Rd, то есть расстояние в пикселях от центра изображения) и нашел формулу для Ru, радиус неискаженного изображения.

Вы хотите пойти в другую сторону. Для каждого пикселя в неискаженном (обработанном изображении) вы хотите знать, что соответствует пикселю в искаженном изображении. Другими словами, данный (xu, yu) -> (xd, yd). Затем вы заменяете каждый пиксель в неискаженном изображении соответствующим пикселем из искаженного изображения.

Начиная с JMBR, я делаю обратное, находя Rd как функцию Ru. Я получаю:

Rd = f * sqrt(2) * sqrt(1 - 1/sqrt(r^2 +1)) 

где F является фокусное расстояние в пикселях (я объясню позже), и r = Ru/f.

Фокусное расстояние для камеры составляет 2,5 мм. Размер каждого пикселя на моей ПЗС составлял 6 мкм. f было поэтому 2500/6 = 417 пикселей. Это можно найти методом проб и ошибок.

Поиск Rd позволяет найти соответствующий пиксель в искаженном изображении с использованием полярных координат.

Угол каждого пикселя от центральной точки одно и то же:

theta = arctan((yu-yc)/(xu-xc)) где хс, Ус является центральными точками.

Затем

xd = Rd * cos(theta) + xc 
yd = Rd * sin(theta) + yc 

Убедитесь, что вы знаете, какой квадрант вы находитесь.

Здесь есть C# код, который я использовал

public class Analyzer 
{ 
     private ArrayList mFisheyeCorrect; 
     private int mFELimit = 1500; 
     private double mScaleFESize = 0.9; 

     public Analyzer() 
     { 
      //A lookup table so we don't have to calculate Rdistorted over and over 
      //The values will be multiplied by focal length in pixels to 
      //get the Rdistorted 
      mFisheyeCorrect = new ArrayList(mFELimit); 
      //i corresponds to Rundist/focalLengthInPixels * 1000 (to get integers) 
      for (int i = 0; i < mFELimit; i++) 
      { 
       double result = Math.Sqrt(1 - 1/Math.Sqrt(1.0 + (double)i * i/1000000.0)) * 1.4142136; 
       mFisheyeCorrect.Add(result); 
      } 
     } 

     public Bitmap RemoveFisheye(ref Bitmap aImage, double aFocalLinPixels) 
     { 
      Bitmap correctedImage = new Bitmap(aImage.Width, aImage.Height); 
      //The center points of the image 
      double xc = aImage.Width/2.0; 
      double yc = aImage.Height/2.0; 
      Boolean xpos, ypos; 
      //Move through the pixels in the corrected image; 
      //set to corresponding pixels in distorted image 
      for (int i = 0; i < correctedImage.Width; i++) 
      { 
       for (int j = 0; j < correctedImage.Height; j++) 
       { 
        //which quadrant are we in? 
        xpos = i > xc; 
        ypos = j > yc; 
        //Find the distance from the center 
        double xdif = i-xc; 
        double ydif = j-yc; 
        //The distance squared 
        double Rusquare = xdif * xdif + ydif * ydif; 
        //the angle from the center 
        double theta = Math.Atan2(ydif, xdif); 
        //find index for lookup table 
        int index = (int)(Math.Sqrt(Rusquare)/aFocalLinPixels * 1000); 
        if (index >= mFELimit) index = mFELimit - 1; 
        //calculated Rdistorted 
        double Rd = aFocalLinPixels * (double)mFisheyeCorrect[index] 
             /mScaleFESize; 
        //calculate x and y distances 
        double xdelta = Math.Abs(Rd*Math.Cos(theta)); 
        double ydelta = Math.Abs(Rd * Math.Sin(theta)); 
        //convert to pixel coordinates 
        int xd = (int)(xc + (xpos ? xdelta : -xdelta)); 
        int yd = (int)(yc + (ypos ? ydelta : -ydelta)); 
        xd = Math.Max(0, Math.Min(xd, aImage.Width-1)); 
        yd = Math.Max(0, Math.Min(yd, aImage.Height-1)); 
        //set the corrected pixel value from the distorted image 
        correctedImage.SetPixel(i, j, aImage.GetPixel(xd, yd)); 
       } 
      } 
      return correctedImage; 
     } 
}