如何使用 OpenCV 从垫子上裁剪任意形状?

Posted

技术标签:

【中文标题】如何使用 OpenCV 从垫子上裁剪任意形状?【英文标题】:How to crop an arbitrary shape from mat using OpenCV? 【发布时间】:2016-10-25 23:44:28 【问题描述】:

我正在使用 CvVideoCamera 的输出到 UIImageView,然后计算轮廓并将最大的轮廓绘制到覆盖的 UIImageView 上。我正在使用矩形 ROI(感兴趣区域)来裁剪图像,以便计算出的轮廓完全在裁剪区域内。现在,对于可以在 iPad 上使用手势绘制的任何形状,我该如何做同样的事情?

使用 OpenCV 3.1

@interface FCCameraViewController() <CvVideoCameraDelegate>

@property (nonatomic, strong) FCVideoCamera *camera;
@property (nonatomic, assign) UIImageOrientation imageOrientation;

@end

@implementation FCCameraViewController

- (void)setStarted:(BOOL)started 
    _started = started;
    if (started) 
        [self.camera start];
     else 
        [self.camera stop];
    


- (void)viewWillLayoutSubviews

    [super viewWillLayoutSubviews];

    self.imageView.frame = self.view.frame;

    NSArray *layers = [[self.imageView layer] sublayers];
    for (CALayer *layer in layers) 
        layer.frame = self.view.frame;
    


- (void)viewDidLoad

    //Camera
    self.camera = [[FCVideoCamera alloc] initWithParentView: self.imageView];
    self.camera.defaultAVCaptureDevicePosition = AVCaptureDevicePositionBack;
    self.camera.defaultAVCaptureSessionPreset = AVCaptureSessionPreset1280x720;
    self.camera.defaultAVCaptureVideoOrientation = AVCaptureVideoOrientationPortrait;
    self.camera.defaultFPS = 30;
    self.camera.useAVCaptureVideoPreviewLayer = NO;
    self.camera.grayscaleMode = NO;
    self.camera.delegate = self;
    self.isAutoEdgeMode = NO;
    self.imageOrientation = UIImageOrientationUp;


- (void)setIsAutoEdgeMode:(BOOL)isAutoEdgeMode

    _isAutoEdgeMode = isAutoEdgeMode;
    if (!self.started && self.imageView.image != nil) 

        cv::Mat mat = [self CVMatFromUIImage:self.imageView.image];
        self.imageOrientation = self.imageView.image.imageOrientation;

        [self processImage:mat];
    


#pragma mark - CvVideoCameraDelegate

cv::Mat original, src_gray, drawing_mat, cropped_area;
int thresh = 192;//100;
int max_thresh = 255;
cv::RNG rng(12345);

- (void)processImage:(cv::Mat &)image

    if (!self.isAutoEdgeMode) 
        return;
    

    original = image.clone();

    cropped_area = [self crop:image];

    cv::cvtColor(cropped_area, src_gray, cv::COLOR_BGR2GRAY);
    cv::blur(src_gray, src_gray, cv::Size(3,3));

    cv::Mat threshold_output;
    std::vector<std::vector<cv::Point> > contours;
    std::vector<cv::Vec4i> hierarchy;

    /// Detect edges using Threshold
    cv::threshold( src_gray, threshold_output, thresh, 255, cv::THRESH_BINARY );

    /// Find contours
    cv::findContours(threshold_output, contours, hierarchy, CV_RETR_TREE, CV_CHAIN_APPROX_SIMPLE, cv::Point(0, 0) );

    // approximate the convex hulls
    std::vector<std::vector<cv::Point>> hull(contours.size());
    for( size_t i = 0; i < contours.size(); i++ ) 
        convexHull( cv::Mat(contours[i]), hull[i], false );
    

    // Approximate contours to polygons + get bounding rects and circles
    std::vector<std::vector<cv::Point> > contours_poly( contours.size() );
    std::vector<cv::Rect> boundRect( contours.size() );
    std::vector<cv::Point2f>center( contours.size() );
    std::vector<float>radius( contours.size() );

    std::vector<cv::Point> *largestContourPoly = nil;
    std::vector<cv::Point> *secondLargest = nil;
    double largestArea = 0;

    for(int i = 0; i < contours.size(); i++ ) 
        std::vector<cv::Point> a_contour = contours[i];
        double contourArea = cv::contourArea(a_contour);
        double size = contourArea;

        // Calculate the polygon based on the contour
        cv::approxPolyDP(cv::Mat(contours[i]), contours_poly[i], 1, true );

        // Get random color
        cv::Scalar color(rand() & 255, rand() & 255, rand() & 255);
        cv::Vec3b otherColor(color[2], color[0], color[1]);

        // DEBUG HELPER: Draw all polygons
        cv::polylines(cropped_area, contours_poly[i], true, color, FCLineWidth);

        // DEBUG HELPER: Draw convex hull
        cv::drawContours(cropped_area, hull, (int)i, RED, 2, 8, std::vector<cv::Vec4i>(), 0, cv::Point() );

        // Save the largest polygon
        if (size > largestArea) 
            largestArea = contourArea;
            secondLargest = largestContourPoly;
            largestContourPoly = &contours_poly[i];
        
    

    // Draw the largest polygon on the non-gray scale
    if (largestContourPoly != NULL) 
        cv::polylines(cropped_area, *largestContourPoly, true, GREEN, FCLineWidth);

        // Show on ImageView if using a still image
        if (!self.started) 
            // use with imageOrientation in mind
            UIImage *convertedImage = [self UIImageFromCVMat:cropped_area];
            self.imageView.image = convertedImage;
        

        // Add back to original image
        UIImage *testCrop = [self UIImageFromCVMat:cropped_area];
        cropped_area.copyTo(image(cv::Rect(300, 300, cropped_area.cols, cropped_area.rows)));

        // Show drawing on other image view
        cv::Mat matContour(image.size(), CV_8UC1);
        matContour = cv::Scalar(255,255,255,0); // make background white
        cv::polylines(matContour, *largestContourPoly, true, GREEN, FCLineWidth);

        // TODO: will need to offset the matContour drawing by the croped_area's offset

        UIImage *autoEdge = [self UIImageFromCVMat:matContour];
        self.autoEdgeImage = [autoEdge replaceColor:[UIColor colorWithRed:1.0f green:1.0f blue:1.0f alpha:1.0] inImage:autoEdge withTolerance:10];
    




#pragma mark - Helpers

- (cv::Mat)crop:(cv::Mat)uncropped 
    // Setup a rectangle to define your region of interest
    cv::Rect myROI(300, 300, 300, 300);

    // Crop the full image to that image contained by the rectangle myROI
    // Note that this doesn't copy the data
    cv::Mat croppedRef = uncropped(myROI);

    // Copy the data into new matrix
    cv::Mat cropped;
    croppedRef.copyTo(cropped);
    UIImage *croppedImage = [self UIImageFromCVMat:cropped];
    return cropped;


-(cv::Mat)CVMatFromUIImage:(UIImage *)image

    CGColorSpaceRef colorSpace = CGImageGetColorSpace(image.CGImage);
    CGFloat cols = image.size.width;
    CGFloat rows = image.size.height;

    if  (image.imageOrientation == UIImageOrientationUp
         || image.imageOrientation == UIImageOrientationDown) 
        cols = image.size.height;
        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;


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

    // Creating CGImage from cv::Mat
    CGImageRef imageRef = CGImageCreate(cvMat.cols,                                 //width
                                        cvMat.rows,                                 //height
                                        8,                                          //bits per component
                                        8 * cvMat.elemSize(),                       //bits per pixel
                                        cvMat.step[0],                            //bytesPerRow
                                        colorSpace,                                 //colorspace
                                        kCGImageAlphaNone|kCGBitmapByteOrderDefault,// bitmap info
                                        provider,                                   //CGDataProviderRef
                                        NULL,                                       //decode
                                        false,                                      //should interpolate
                                        kCGRenderingIntentDefault                   //intent
                                        );


    // Getting UIImage from CGImage
    UIImage *finalImage = [UIImage imageWithCGImage:imageRef scale:1 orientation:self.imageOrientation];


    CGImageRelease(imageRef);
    CGDataProviderRelease(provider);
    CGColorSpaceRelease(colorSpace);

    return finalImage;


@end

【问题讨论】:

只是一个想法 - 我建议您检查一个像素是否属于用户定义的区域。如果是,则保留像素值,否则使用 Alpha 通道播放或将像素设置为黑色。图像的形状只能是矩形,也就是说您可以根据区域的最小值和最大值进行裁剪。按照我上面的建议在里面做。 【参考方案1】:

我最终将converting UIBezierPath 用于CGPoints 数组。使用这些点,我使用 cv::drawContours 绘制区域并创建蒙版。最后,我返回感兴趣的区域,现在我用它来计算我原始帖子中的最大轮廓区域。

- (cv::Mat)cropArbitrary:(cv::Mat)uncropped 

    cv::Mat dst(uncropped.rows, uncropped.cols, uncropped.type(), cv::Scalar(0));
    int new_w=0;
    int new_h=0;
    if(uncropped.cols>dst.cols)
        new_w=dst.cols;
    else
        new_w=uncropped.cols;

    if(uncropped.rows>dst.rows)
        new_h=dst.rows;
    else
        new_h=uncropped.rows;

    cv::Rect rectROI(0,0,uncropped.cols,uncropped.rows);
    cv::Mat mask(uncropped.rows, uncropped.cols, CV_8UC1, cv::Scalar(0));

    std::vector< std::vector<cv::Point> >  co_ordinates;
    co_ordinates.push_back(std::vector<cv::Point>());

    for (int i = 0; i < self.points.count; i++) 
        NSValue *value = self.points[i];
        CGPoint point = value.CGPointValue;
        cv::Point newPoint(point.x, point.y);
        co_ordinates[0].push_back(newPoint);
    

    cv::drawContours( mask,co_ordinates,0, cv::Scalar(255),CV_FILLED, 8 );

    cv::Mat srcROI = uncropped(rectROI);
    cv::Mat dstROI = dst(rectROI);
    cv::Mat dst1;
    cv::Mat dst2;

    srcROI.copyTo(dst1,mask);

    bitwise_not(mask,mask);
    dstROI.copyTo(dst2,mask);

    dstROI.setTo(0);
    dstROI=dst1+dst2;
    return dst1;

【讨论】:

以上是关于如何使用 OpenCV 从垫子上裁剪任意形状?的主要内容,如果未能解决你的问题,请参考以下文章

OpenCV - 如何计算垫子的对数?

OpenCV - 检测矩形或五边形

如何通过另一个具有位置(索引)的垫子访问opencv中的矩阵数据

OpenCV 将任意轮廓的形状转换为矩形

python 使用opencv库通过智能裁剪生成给定形状的缩略图。

从 OpenCV 3 中的 VideoCapture 获取垫子