سؤال

تحديث حالة المكافأة:

اكتشفت كيفية تعيين عدسة خطية, ، من destination ينسق source الإحداثيات.

كيف تحسب المسافة الشعاعية من الوسط للانتقال من فيش إلى مستقيم؟

  • 1). أنا في الواقع أكافح من أجل عكسها ، ورسم خريطة الإحداثيات المصدر إلى إحداثيات الوجهة. ما هو العكس ، في الكود في نمط وظائف التحويل التي نشرتها؟

  • 2). أرى أيضًا أن عدم تنويمي غير ناقص على بعض العدسات - من المفترض أن تكون خطيًا تمامًا. ما هي إحداثيات المصدر والتقدم المكافئة لتلك العدسات؟ مرة أخرى ، رمز أكثر من الصيغ الرياضية فقط من فضلك ...


سؤال كما ذكر في الأصل:

لدي بعض النقاط التي تصف المواقف في صورة تم التقاطها مع عدسة فيش.

أريد تحويل هذه النقاط إلى إحداثيات مستقيمة. أريد أن أزيل الصورة.

لقد وجدت هذا الوصف من كيفية توليد تأثير فيش ، ولكن ليس كيفية عكس ذلك.

هناك أيضا مشاركة مدونة التي تصف كيفية استخدام الأدوات للقيام بذلك ؛ هذه الصور من ذلك:

(1) : SOURCE رابط الصور الأصلي

إدخال : الصورة الأصلية مع تشويه عين السمك لإصلاح.

(2) : DESTINATION رابط الصور الأصلي

انتاج : تصحيح الصورة (من الناحية الفنية أيضًا مع تصحيح المنظور ، ولكن هذه خطوة منفصلة).

كيف تحسب المسافة الشعاعية من الوسط للانتقال من فيش إلى مستقيم؟

يبدو أن كعب وظيفتي يشبه هذا:

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. هل هناك طريقة مباشرة للقيام بذلك في OpenCV ، وهل يعمل بشكل جيد بما يكفي للقيام بذلك في تغذية فيديو حية؟

هل كانت مفيدة؟

المحلول

ال الوصف الذي ذكرته ينص على أن الإسقاط بواسطة كاميرا ثقب (واحدة لا تقدم تشويه العدسة) على غرار

R_u = f*tan(theta)

ويتم تصميم الإسقاط من قبل كاميرات عدسة فيش الشائعة (أي ، مشوهة)

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

أنت تعرف بالفعل R_D و Theta ، وإذا كنت تعرف طول البعد البؤري للكاميرا (ممثلة بـ F) ، فإن تصحيح الصورة سيكون بمثابة حساب R_U من حيث R_D و Theta. بعبارات أخرى،

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

هي الصيغة التي تبحث عنها. يمكن حل تقدير الطول البؤري F عن طريق معايرة الكاميرا أو أي وسائل أخرى مثل السماح للمستخدم بتقديم ملاحظات حول مدى تصحيح الصورة أو استخدام المعرفة من المشهد الأصلي.

من أجل حل نفس المشكلة باستخدام OpenCV ، يجب عليك الحصول على المعلمات الجوهرية للكاميرا ومعاملات تشويه العدسة. انظر ، على سبيل المثال ، الفصل 11 من تعلم opencv (لا تنس التحقق من تصحيح). ثم يمكنك استخدام برنامج مثل هذا البرنامج (مكتوب مع روابط 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 يستخدم نموذج تشويه عدسة مختلف تمامًا للنواة الموجودة في صفحة الويب التي ربطتها.

نصائح أخرى

(الملصق الأصلي ، يوفر بديلاً)

الإحداثيات الوجهة الوظيفية التالية (المستقيمة) لإحداثيات المصدر (Fishee-Distorted). (أقدر المساعدة في عكسها)

وصلت إلى هذه النقطة من خلال المحاكمة والخطأ: لا أفهم بشكل أساسي سبب عمل هذا الرمز ، التفسيرات وتحسين الدقة موضع تقدير!

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 ، فإنه ينجح في عدم التغلب على الصور المستخدمة كأمثلة (لم أجرب أي محاولة لاستيفاء الجودة):

رابط ميت

(وهذا من منشور المدونة ، للمقارنة :)

Using Panotools

إذا كنت تعتقد أن صيغك دقيقة ، فيمكنك حساب صيغة دقيقة مع Trig ، مثل ذلك:

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 + ...)

من خلال التغيير الأول في المعاملات A ، ثم ، يمكنك حساب أي وظيفة محلية معقولة (يستفيد شكل التوسع من تناسق المشكلة). على وجه الخصوص ، يجب أن يكون من الممكن حساب المعاملات الأولية لتقريب الوظيفة النظرية أعلاه.

أيضًا ، للحصول على نتائج جيدة ، ستحتاج إلى استخدام مرشح الاستيفاء لإنشاء صورتك المصححة. طالما أن التشويه ليس كبيرًا جدًا ، يمكنك استخدام نوع المرشح الذي ستستخدمه لإنقاذ الصورة خطيًا دون مشكلة كبيرة.

تحرير: حسب طلبك ، عامل التحجيم المكافئ للصيغة أعلاه:

(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)

إذا قمت برسم الصيغة أعلاه إلى جانب Tan (Rin/F) ، فيمكنك أن ترى أنها متشابهة جدًا في الشكل. في الأساس ، يصبح التشويه من الظل شديدًا قبل أن يصبح الخطيئة (W) مختلفة كثيرًا عن W.

يجب أن تكون الصيغة العكسية مثل:

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

لقد قمت بتنفيذ الصيغ من هنا, ، لذلك لا يمكنني ضمان أن يفعل ما تحتاجه.

يستخدم 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

أخذت ما فعله 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 مم. كان حجم كل بكسل على CCD الخاص بي 6 أم مربع. كان F لذلك 2500/6 = 417 بكسل. هذا يمكن العثور عليه عن طريق التجربة والخطأ.

يتيح لك العثور على RD العثور على البكسل المقابل في الصورة المشوهة باستخدام الإحداثيات القطبية.

زاوية كل بكسل من نقطة الوسط هي نفسها:

theta = arctan( (yu-yc)/(xu-xc) ) حيث XC ، YC هي نقاط المركز.

ثم،

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;
      }
}

لقد وجدت ملف PDF هذا وأثبتت أن الرياضيات صحيحة (باستثناء السطر vd = *xd**fv+v0 which should say vd = **yd**+fv+v0).

http://perception.inrialpes.fr/cava_dataset/site/files/calibration_opencv.pdf

لا يستخدم جميع أحدث الآثار المشتركة التي توفرها 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;
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top