Search code examples
iosswiftcidetector

Convert from CIRectangleFeature coordinates to view coordinates


I am using the CIDetector class in IOS to find CIRectangleFeatures in my UIImage. Afterwards, I aim to show the cornerPoints drawn into a layer which I then, in turn, add to my UIImageView. Unfortunately, the coordinates given with the CIRectangleFeature are in image space. And, even though I am trying to convert them using the CGContextConvertToUserSpace function, the rectangle drawn is quite off of the actual rectangle in the image.

Here is my code, when an image is taken, workWithTheImage is called:

func analyzeImage(image: UIImage) -> [RectanglePoint]
    {
        guard let ciImage = CIImage(image: image)
            else { return [] }

        let context = CIContext(options: nil)

        let detector = CIDetector(ofType: CIDetectorTypeRectangle, context: context, options: nil)

        let features = detector.featuresInImage(ciImage)

        UIGraphicsBeginImageContext(ciImage.extent.size)
        let currentContext = UIGraphicsGetCurrentContext()

        var points: [RectanglePoint] = []

        for feature in features as! [CIRectangleFeature]
        {
            let topLeft = CGContextConvertPointToUserSpace(currentContext, feature.topLeft)
            let topRight = CGContextConvertPointToUserSpace(currentContext, feature.topRight)
            let bottomRight = CGContextConvertPointToUserSpace(currentContext, feature.bottomRight)
            let bottomLeft = CGContextConvertPointToUserSpace(currentContext, feature.bottomLeft)

            let point = RectanglePoint(bottomLeft: bottomLeft, topLeft: topLeft, bottomRight: bottomRight, topRight: topRight)
            points.append(point)
        }

        UIGraphicsEndImageContext()
        return points
    }



    func workWithImage(image: UIImage)
    {
        let imageView = UIImageView(frame: view.frame)
        imageView.image = image

        let path = UIBezierPath()
        let shapeLayer = CAShapeLayer()
        shapeLayer.frame = imageView.bounds

        for i in analyzeImage(image)
        {
            path.moveToPoint(i.topLeft)
            path.addLineToPoint(i.topRight)
            path.addLineToPoint(i.bottomRight)
            path.addLineToPoint(i.bottomLeft)
            path.addLineToPoint(i.topLeft)
        }

        shapeLayer.path = path.CGPath
        shapeLayer.fillColor = UIColor.clearColor().CGColor
        shapeLayer.strokeColor = UIColor.redColor().CGColor

        imageView.layer.addSublayer(shapeLayer)
    }

Solution

  • If your path is only off in the Y dimension:

    I think they haven't fully ported CIDetector to UIKit yet. The coordinates of the feature are in the Cocoa coordinate system. Simply doing container.height - point.y will convert it.

    I also gave your struct the correct name. The rest of the stuff in there, I used to figure out what was going on. Might be useful to you.

    Code :

    func analyzeImage(image: UIImage) -> [Quadrilateral]
    {
        guard let ciImage = CIImage(image: image)
            else { return [] }
    
        let flip = true // set to false to prevent flipping the coordinates
    
        let context = CIContext(options: nil)
    
        let detector = CIDetector(ofType: CIDetectorTypeRectangle, context: context, options: [CIDetectorAccuracy:CIDetectorAccuracyHigh])
    
        let features = detector.featuresInImage(ciImage)
    
        UIGraphicsBeginImageContext(ciImage.extent.size)
        let currentContext = UIGraphicsGetCurrentContext()
    
        var frames: [Quadrilateral] = []
    
        for feature in features as! [CIRectangleFeature]
        {
            var topLeft = CGContextConvertPointToUserSpace(currentContext, feature.topLeft)
            var topRight = CGContextConvertPointToUserSpace(currentContext, feature.topRight)
            var bottomRight = CGContextConvertPointToUserSpace(currentContext, feature.bottomRight)
            var bottomLeft = CGContextConvertPointToUserSpace(currentContext, feature.bottomLeft)
    
            if flip {
                topLeft = CGPoint(x: topLeft.x, y: image.size.height - topLeft.y)
                topRight = CGPoint(x: topRight.x, y: image.size.height - topRight.y)
                bottomLeft = CGPoint(x: bottomLeft.x, y: image.size.height - bottomLeft.y)
                bottomRight = CGPoint(x: bottomRight.x, y: image.size.height - bottomRight.y)
            }
    
            let frame = Quadrilateral(topLeft: topLeft, topRight: topRight, bottomLeft: bottomLeft, bottomRight: bottomRight)
    
            frames.append(frame)
        }
    
        UIGraphicsEndImageContext()
        return frames
    }
    

    Quadrilateral struct :

    struct Quadrilateral {
        var topLeft : CGPoint = CGPointZero
        var topRight : CGPoint = CGPointZero
        var bottomLeft : CGPoint = CGPointZero
        var bottomRight : CGPoint = CGPointZero
    
        var path : UIBezierPath {
            get {
                let tempPath = UIBezierPath()
                tempPath.moveToPoint(topLeft)
                tempPath.addLineToPoint(topRight)
                tempPath.addLineToPoint(bottomRight)
                tempPath.addLineToPoint(bottomLeft)
                tempPath.addLineToPoint(topLeft)
                return tempPath
            }
        }
    
        init(topLeft topLeft_I: CGPoint, topRight topRight_I: CGPoint, bottomLeft bottomLeft_I: CGPoint, bottomRight bottomRight_I: CGPoint) {
            topLeft = topLeft_I
            topRight = topRight_I
            bottomLeft = bottomLeft_I
            bottomRight = bottomRight_I
        }
    
        var frame : CGRect {
            get {
                let highestPoint = max(topLeft.y, topRight.y, bottomLeft.y, bottomRight.y)
                let lowestPoint = min(topLeft.y, topRight.y, bottomLeft.y, bottomRight.y)
                let farthestPoint = max(topLeft.x, topRight.x, bottomLeft.x, bottomRight.x)
                let closestPoint = min(topLeft.x, topRight.x, bottomLeft.x, bottomRight.x)
    
                // you might want to set origin to (0,0)
                let origin = CGPoint(x: closestPoint, y: lowestPoint)
                let size = CGSize(width: farthestPoint, height: highestPoint)
    
                return CGRect(origin: origin, size: size)
            }
        }
    
        var size : CGSize {
            get {
                return frame.size
            }
        }
    
        var origin : CGPoint {
            get {
                return frame.origin
            }
        }
    }