2012-11-07 4 views
49

J'essaie d'implémenter une fonction de correction de perspective de recadrage & dans une application à venir. Tout en faisant des recherches, je suis tombé sur:Perspective Transformer + Recadrer dans iOS avec OpenCV

Executing cv::warpPerspective for a fake deskewing on a set of cv::Point

http://sudokugrab.blogspot.ch/2009/07/how-does-it-all-work.html

J'ai donc décidé d'essayer l'application de cette fonctionnalité avec OpenCV - le cadre est là pour l'installation a été rapide. Cependant, je ne reçois pas les résultats que j'espériez: (2ème photo est le résultat)

Original Photo & cropping box

Cropped Photo, bad result

Je traduisais tout le ai code pour travailler avec Xcode et triples ont vérifié les coordonnées . Pouvez-vous me dire ce qui ne va pas avec mon code? Par souci d'exhaustivité, je l'ai également inclus le UIImage -> conversion Mat + inversion:

- (void)confirmedImage 
{ 
    if ([_adjustRect frameEdited]) { 

    cv::Mat src = [self cvMatFromUIImage:_sourceImage]; 

    // My original Coordinates 
    // 4-------3 
    // |  | 
    // |  | 
    // |  | 
    // 1-------2 

    CGFloat scaleFactor = [_sourceImageView contentScale]; 
    CGPoint p1 = [_adjustRect coordinatesForPoint:4 withScaleFactor:scaleFactor]; 
    CGPoint p2 = [_adjustRect coordinatesForPoint:3 withScaleFactor:scaleFactor]; 
    CGPoint p3 = [_adjustRect coordinatesForPoint:1 withScaleFactor:scaleFactor]; 
    CGPoint p4 = [_adjustRect coordinatesForPoint:2 withScaleFactor:scaleFactor]; 

    std::vector<cv::Point2f> c1; 
    c1.push_back(cv::Point2f(p1.x, p1.y)); 
    c1.push_back(cv::Point2f(p2.x, p2.y)); 
    c1.push_back(cv::Point2f(p3.x, p3.y)); 
    c1.push_back(cv::Point2f(p4.x, p4.y)); 

    cv::RotatedRect box = minAreaRect(cv::Mat(c1)); 
    cv::Point2f pts[4]; 
    box.points(pts); 

    cv::Point2f src_vertices[3]; 
    src_vertices[0] = pts[0]; 
    src_vertices[1] = pts[1]; 
    src_vertices[2] = pts[3]; 

    cv::Point2f dst_vertices[4]; 
    dst_vertices[0].x = 0; 
    dst_vertices[0].y = 0; 

    dst_vertices[1].x = box.boundingRect().width-1; 
    dst_vertices[1].y = 0; 

    dst_vertices[2].x = 0; 
    dst_vertices[2].y = box.boundingRect().height-1; 

    dst_vertices[3].x = box.boundingRect().width-1; 
    dst_vertices[3].y = box.boundingRect().height-1; 

    cv::Mat warpAffineMatrix = getAffineTransform(src_vertices, dst_vertices); 

    cv::Mat rotated; 
    cv::Size size(box.boundingRect().width, box.boundingRect().height); 
    warpAffine(src, rotated, warpAffineMatrix, size, cv::INTER_LINEAR, cv::BORDER_CONSTANT); 


    [_sourceImageView setNeedsDisplay]; 
    [_sourceImageView setImage:[self UIImageFromCVMat:rotated]]; 
    [_sourceImageView setContentMode:UIViewContentModeScaleAspectFit]; 

    rotated.release(); 
    src.release(); 

    } 
} 

- (UIImage *)UIImageFromCVMat:(cv::Mat)cvMat 
{ 
    NSData *data = [NSData dataWithBytes:cvMat.data length:cvMat.elemSize()*cvMat.total()]; 
    CGColorSpaceRef colorSpace; 
    if (cvMat.elemSize() == 1) { 
     colorSpace = CGColorSpaceCreateDeviceGray(); 
    } 
    else { 
     colorSpace = CGColorSpaceCreateDeviceRGB(); 
    } 
    CGDataProviderRef provider = CGDataProviderCreateWithCFData((__bridge CFDataRef)data); 
    CGImageRef imageRef = CGImageCreate(cvMat.cols, cvMat.rows, 8, 8 * cvMat.elemSize(), cvMat.step[0], colorSpace, kCGImageAlphaNone|kCGBitmapByteOrderDefault, provider, NULL, false, kCGRenderingIntentDefault); 
    UIImage *finalImage = [UIImage imageWithCGImage:imageRef]; 
    CGImageRelease(imageRef); 
    CGDataProviderRelease(provider); 
    CGColorSpaceRelease(colorSpace); 
    return finalImage; 
} 

- (cv::Mat)cvMatFromUIImage:(UIImage *)image 
{ 
    CGColorSpaceRef colorSpace = CGImageGetColorSpace(image.CGImage); 
    CGFloat cols = image.size.width; 
    CGFloat rows = image.size.height; 
    cv::Mat cvMat(rows, cols, CV_8UC4); 
    CGContextRef contextRef = CGBitmapContextCreate(cvMat.data, cols, rows, 8, cvMat.step[0], colorSpace, kCGImageAlphaNoneSkipLast | kCGBitmapByteOrderDefault); 
    CGContextDrawImage(contextRef, CGRectMake(0, 0, rows, cols), image.CGImage); 
    CGContextRelease(contextRef); 
    CGColorSpaceRelease(colorSpace); 
    return cvMat; 
} 

Est-ce la bonne approche à mon problème? Avez-vous un exemple de code qui pourrait m'aider?

Merci d'avoir lu ma question!

udate:

J'ai fait mon remplacement ouvert le code source de UIImagePickerController ici: https://github.com/mmackh/MAImagePickerController-of-InstaPDF qui comprend le point de vue cultural réglable, les filtres et la correction de perspective.

+2

Votre question est bien cadrée! –

+1

Merci, une fois terminé, je prévois de publier tout le code source pour aider les autres aussi. Ça me donne des maux de tête en ce moment. – mmackh

+0

Votre question concerne une transformation de perspective, mais votre code utilise getAffineTransform et warpAffine. –

Répondre

49

Ainsi, après quelques jours d'essayer de le résoudre, je suis venu avec une solution (Ignorer les points bleus sur la deuxième image):

Original Image, with adjusted frame Cropped Frame

Comme promis, voici une copie complète du code:

- (void)confirmedImage 
{ 
    cv::Mat originalRot = [self cvMatFromUIImage:_sourceImage]; 
    cv::Mat original; 
    cv::transpose(originalRot, original); 

    originalRot.release(); 

    cv::flip(original, original, 1); 


    CGFloat scaleFactor = [_sourceImageView contentScale]; 

    CGPoint ptBottomLeft = [_adjustRect coordinatesForPoint:1 withScaleFactor:scaleFactor]; 
    CGPoint ptBottomRight = [_adjustRect coordinatesForPoint:2 withScaleFactor:scaleFactor]; 
    CGPoint ptTopRight = [_adjustRect coordinatesForPoint:3 withScaleFactor:scaleFactor]; 
    CGPoint ptTopLeft = [_adjustRect coordinatesForPoint:4 withScaleFactor:scaleFactor]; 

    CGFloat w1 = sqrt(pow(ptBottomRight.x - ptBottomLeft.x , 2) + pow(ptBottomRight.x - ptBottomLeft.x, 2)); 
    CGFloat w2 = sqrt(pow(ptTopRight.x - ptTopLeft.x , 2) + pow(ptTopRight.x - ptTopLeft.x, 2)); 

    CGFloat h1 = sqrt(pow(ptTopRight.y - ptBottomRight.y , 2) + pow(ptTopRight.y - ptBottomRight.y, 2)); 
    CGFloat h2 = sqrt(pow(ptTopLeft.y - ptBottomLeft.y , 2) + pow(ptTopLeft.y - ptBottomLeft.y, 2)); 

    CGFloat maxWidth = (w1 < w2) ? w1 : w2; 
    CGFloat maxHeight = (h1 < h2) ? h1 : h2; 

    cv::Point2f src[4], dst[4]; 
    src[0].x = ptTopLeft.x; 
    src[0].y = ptTopLeft.y; 
    src[1].x = ptTopRight.x; 
    src[1].y = ptTopRight.y; 
    src[2].x = ptBottomRight.x; 
    src[2].y = ptBottomRight.y; 
    src[3].x = ptBottomLeft.x; 
    src[3].y = ptBottomLeft.y; 

    dst[0].x = 0; 
    dst[0].y = 0; 
    dst[1].x = maxWidth - 1; 
    dst[1].y = 0; 
    dst[2].x = maxWidth - 1; 
    dst[2].y = maxHeight - 1; 
    dst[3].x = 0; 
    dst[3].y = maxHeight - 1; 

    cv::Mat undistorted = cv::Mat(cvSize(maxWidth,maxHeight), CV_8UC1); 
    cv::warpPerspective(original, undistorted, cv::getPerspectiveTransform(src, dst), cvSize(maxWidth, maxHeight)); 

    UIImage *newImage = [self UIImageFromCVMat:undistorted]; 

    undistorted.release(); 
    original.release(); 

    [_sourceImageView setNeedsDisplay]; 
    [_sourceImageView setImage:newImage]; 
    [_sourceImageView setContentMode:UIViewContentModeScaleAspectFit]; 

} 

- (UIImage *)UIImageFromCVMat:(cv::Mat)cvMat 
{ 
    NSData *data = [NSData dataWithBytes:cvMat.data length:cvMat.elemSize() * cvMat.total()]; 

    CGColorSpaceRef colorSpace; 

    if (cvMat.elemSize() == 1) { 
     colorSpace = CGColorSpaceCreateDeviceGray(); 
    } else { 
     colorSpace = CGColorSpaceCreateDeviceRGB(); 
    } 

    CGDataProviderRef provider = CGDataProviderCreateWithCFData((__bridge CFDataRef)data); 

    CGImageRef imageRef = CGImageCreate(cvMat.cols,          // Width 
             cvMat.rows,          // Height 
             8,            // Bits per component 
             8 * cvMat.elemSize(),       // Bits per pixel 
             cvMat.step[0],         // Bytes per row 
             colorSpace,          // Colorspace 
             kCGImageAlphaNone | kCGBitmapByteOrderDefault, // Bitmap info flags 
             provider,          // CGDataProviderRef 
             NULL,           // Decode 
             false,           // Should interpolate 
             kCGRenderingIntentDefault);      // Intent 

    UIImage *image = [[UIImage alloc] initWithCGImage:imageRef]; 
    CGImageRelease(imageRef); 
    CGDataProviderRelease(provider); 
    CGColorSpaceRelease(colorSpace); 

    return image; 
} 

- (cv::Mat)cvMatFromUIImage:(UIImage *)image 
{ 
    CGColorSpaceRef colorSpace = CGImageGetColorSpace(image.CGImage); 
    CGFloat cols = image.size.height; 
    CGFloat rows = image.size.width; 

    cv::Mat cvMat(rows, cols, CV_8UC4); // 8 bits per component, 4 channels 

    CGContextRef contextRef = CGBitmapContextCreate(cvMat.data,     // Pointer to backing data 
                cols,      // Width of bitmap 
                rows,      // Height of bitmap 
                8,       // Bits per component 
                cvMat.step[0],    // Bytes per row 
                colorSpace,     // Colorspace 
                kCGImageAlphaNoneSkipLast | 
                kCGBitmapByteOrderDefault); // Bitmap info flags 

    CGContextDrawImage(contextRef, CGRectMake(0, 0, cols, rows), image.CGImage); 
    CGContextRelease(contextRef); 

    return cvMat; 
} 

Espérons qu'il vous aide + codage heureux!

+0

Qu'est-ce que _adjustRect dans votre code? Et coordonnéesForPoint est votre fonction ou sa fonction intégrée sdk ios? –

+0

Pouvez-vous s'il vous plaît fournir cette fonction? –

+0

J'apprécierais de voir votre méthode de coordonnéesForPoint. J'ai essayé d'entrer dans les coords de type iOS (origine en haut à gauche) mais obtenir des résultats bizarres. – VaporwareWolf

1

Je pense que la correspondance de point dans getAffineTransform est incorrecte.

Vérifiez les coordonnées du point de sortie par box.points(pts);

Pourquoi ne pas simplement utiliser p1 p2 p3 p4 pour calculer la transformation?

+0

J'ai utilisé la solution de karlphillip, où il déclare que "l'ordre des points est important pour getPerspectiveTransform() et doit être spécifié dans l'ordre suivant:" (http://stackoverflow.com/a/7878210/1091044) – mmackh

+0

L'ordre des points produits par RotatedRect :: points() peut être différent de ce que vous supposez. C'est-à-dire, pour un petit déplacement de points d'entrée, minAreaRect() :: points() peut fournir des points dans un ordre différent. – luhb

Questions connexes