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