2016-07-06 3 views
16

В настоящее время я использую CIDetector для обнаружения прямоугольников в моем UIImage. Я делаю предложенный способ передачи координат в фильтр, чтобы вернуть CIImage, чтобы переместить полученный UIImage. Это выглядит следующим образом:Представьте CIRectangleFeature с UIBezierPath - Swift

func performRectangleDetection(image: UIKit.CIImage) -> UIKit.CIImage? { 
    var resultImage: UIKit.CIImage? 
    let detector:CIDetector = CIDetector(ofType: CIDetectorTypeRectangle, context: nil, options: [CIDetectorAccuracy : CIDetectorAccuracyHigh]) 
     // Get the detections 
     let features = detector.featuresInImage(image) 
     for feature in features as! [CIRectangleFeature] { 
      resultImage = self.drawHighlightOverlayForPoints(image, topLeft: feature.topLeft, topRight: feature.topRight, 
                 bottomLeft: feature.bottomLeft, bottomRight: feature.bottomRight) 
     } 
    return resultImage 

} 


func drawHighlightOverlayForPoints(image: UIKit.CIImage, topLeft: CGPoint, topRight: CGPoint, 
            bottomLeft: CGPoint, bottomRight: CGPoint) -> UIKit.CIImage { 

    var overlay = UIKit.CIImage(color: CIColor(red: 1.0, green: 0.55, blue: 0.0, alpha: 0.45)) 
    overlay = overlay.imageByCroppingToRect(image.extent) 
    overlay = overlay.imageByApplyingFilter("CIPerspectiveTransformWithExtent", 
              withInputParameters: [ 
               "inputExtent": CIVector(CGRect: image.extent), 
               "inputTopLeft": CIVector(CGPoint: topLeft), 
               "inputTopRight": CIVector(CGPoint: topRight), 
               "inputBottomLeft": CIVector(CGPoint: bottomLeft), 
               "inputBottomRight": CIVector(CGPoint: bottomRight) 
     ]) 
    return overlay.imageByCompositingOverImage(image) 
} 

Вызов performRectangleDetection отображает обнаруженный прямоугольник через CIImage.

Это похоже на изображение выше. Мне нужно отобразить тот же красный прямоугольник с UIBezierPath, который настроен на обводку. Мне нужно иметь это, чтобы пользователь мог настроить обнаружение, если он не на 100% точным. Я попытался проложить путь, но он не увенчался успехом. Вот как я рисую путь. Я использую собственный класс rect, чтобы удерживать 4 балла. Вот обнаружение:

func detectRect() -> Rect{ 
    var rect:Rect? 
    let detector:CIDetector = CIDetector(ofType: CIDetectorTypeRectangle, context: nil, options: [CIDetectorAccuracy : CIDetectorAccuracyHigh]) 
    // Get the detections 
    let features = detector.featuresInImage(UIKit.CIImage(image: self)!) 
    for feature in features as! [CIRectangleFeature] { 
     rect = Rect(tL: feature.topLeft, tR: feature.topRight, bR: feature.bottomRight, bL: feature.bottomLeft) 
    } 
    return rect! 
} 

Далее я должен масштабировать координаты. Вот функция внутри класса Rect, что делает это:

func scaleRect(image:UIImage, imageView:UIImageView) ->Rect{ 

    let scaleX = imageView.bounds.width/image.size.width 
    var tlx = topLeft.x * scaleX 
    var tly = topLeft.y * scaleX 
    tlx += (imageView.bounds.width - image.size.width * scaleX)/2.0 
    tly += (imageView.bounds.height - image.size.height * scaleX)/2.0 
    let tl = CGPointMake(tlx, tly) 

    var trx = topRight.x * scaleX 
    var trY = topRight.y * scaleX 
    trx += (imageView.bounds.width - image.size.width * scaleX)/2.0 
    trY += (imageView.bounds.height - image.size.height * scaleX)/2.0 
    let tr = CGPointMake(trx, trY) 

    var brx = bottomRight.x * scaleX 
    var bry = bottomRight.y * scaleX 
    brx += (imageView.bounds.width - image.size.width * scaleX)/2.0 
    bry += (imageView.bounds.height - image.size.height * scaleX)/2.0 
    let br = CGPointMake(brx, bry) 

    var blx = bottomLeft.x * scaleX 
    var bly = bottomLeft.y * scaleX 
    blx += (imageView.bounds.width - image.size.width * scaleX)/2.0 
    bly += (imageView.bounds.height - image.size.height * scaleX)/2.0 
    let bl = CGPointMake(blx, bly) 

    let rect = Rect(tL: tl, tR: tr, bR: br, bL: bl) 
    return rect 
} 

Наконец я рисую путь:

var tet = image.detectRect() 
tet = tet.scaleRect(image, imageView: imageView) 
let shapeLayer = CAShapeLayer() 
let path = ViewController.drawPath(tet.topLeft, p2: tet.topRight, p3: tet.bottomRight, p4: tet.bottomLeft) 
shapeLayer.path = path.CGPath 
shapeLayer.lineWidth = 5 
shapeLayer.fillColor = nil 
shapeLayer.strokeColor = UIColor.orangeColor().CGColor 
imageView.layer.addSublayer(shapeLayer) 

Путь был выключен экран и неточными. Я знаю, что мне нужно настроить координаты координат CoreImage на координаты UIKit, а затем масштабировать их для UIImageView. К сожалению, я не знаю, как это сделать. Я знаю, что могу повторно использовать некоторые из моих кодов обнаружения, чтобы выполнить это, но я не знаю правильных шагов. Любая помощь будет оценена! Благодарю. Вот пример того, что происходящий:

Update

Для того, чтобы испытывать мое масштабирование, что я выступаю в scaleRect() я решил сделать свой размер ImageView такого же размера, как мой Размер изображения. Затем я напечатал координаты до и после масштабирования. Я думаю, что, поскольку они одинаковы, мое масштабирование выполняется правильно. Вот код:

var tet = image.detectRect() 
//Before scaling 
print(tet.topLeft) 
print(tet.topRight) 
print(tet.bottomRight) 
print(tet.bottomLeft) 
print("**************************************************") 
//After scaling 
tet = tet.scaleRect(image, imageView: imageView) 
print(tet.topLeft) 
print(tet.topRight) 
print(tet.bottomRight) 
print(tet.bottomLeft) 

Здесь выход:

(+742,386596679688, 927,240844726562)

(1514,93835449219, 994,811096191406)

(1514,29675292969, 155,2802734375)

(741,837524414062 , 208.55403137207)


(742,386596679688, 927,240844726562)

(1514,93835449219, 994,811096191406)

(1514,29675292969, 155,2802734375)

(741,837524414062, 208.55403137207)

Update

Я попробовал еще две вещи, чтобы попытаться и масштабировать свои координаты.

Номер 1: Я попытался использовать функцию преобразования UIView для преобразования точки с изображения в UIImageView. Вот как я закодировал: я заменил функцию scaleRect() с

let view_image = UIView(frame: CGRectMake(0, 0, image.size.width, image.size.height)) 
let tL = view_image.convertPoint(self.topLeft, toView: imageView) 
let tR = view_image.convertPoint(self.topRight, toView: imageView) 
let bR = view_image.convertPoint(self.bottomRight, toView: imageView) 
let bL = view_image.convertPoint(self.bottomLeft, toView: imageView) 

Затем я вернулся новый прямоугольник с этими точками.

Номер 2: Я пробовал простой перевод координат, основанный на различии в ширине и высоте изображения и изображения. Heres код:

let widthDiff = (image.size.width - imageView.frame.size.width) 
let highDiff = (image.size.height - imageView.frame.size.height) 

let tL = CGPointMake(self.topLeft.x-widthDiff, self.topLeft.y-highDiff) 
let tR = CGPointMake(self.topRight.x-widthDiff, self.topRight.y-highDiff) 
let bR = CGPointMake(self.bottomRight.x-widthDiff, self.bottomRight.y-highDiff) 
let bL = CGPointMake(self.bottomLeft.x-widthDiff, self.bottomLeft.y-highDiff) 

Update Я также попытался с помощью CGAffineTransform. Код:

var transform = CGAffineTransformMakeScale(1, -1) 
transform = CGAffineTransformTranslate(transform, 0, -imageView.bounds.size.height) 
let tL = CGPointApplyAffineTransform(self.topLeft, transform) 
let tR = CGPointApplyAffineTransform(self.topRight, transform) 
let bR = CGPointApplyAffineTransform(self.bottomRight, transform) 
let bL = CGPointApplyAffineTransform(self.bottomLeft, transform) 

Ничего не работало. Я не знаю, что еще я могу попробовать. Пожалуйста помоги. Было бы весьма полезно. Благодаря!

ответ

3

Я боролся с той же проблемой в течение нескольких дней, и это, как я преодолел эту проблему:

Я сделал пользовательский класс для хранения очков и добавить некоторые вспомогательные функции:

// 
// ObyRectangleFeature.swift 
// 
// Created by 4oby on 5/20/16. 
// Copyright © 2016 cvv. All rights reserved. 
// 

import Foundation 
import UIKit 

extension CGPoint { 
    func scalePointByCeficient(ƒ_x: CGFloat, ƒ_y: CGFloat) -> CGPoint { 
     return CGPoint(x: self.x/ƒ_x, y: self.y/ƒ_y) //original image 
    } 

    func reversePointCoordinates() -> CGPoint { 
     return CGPoint(x: self.y, y: self.x) 
    } 

    func sumPointCoordinates(add: CGPoint) -> CGPoint { 
     return CGPoint(x: self.x + add.x, y: self.y + add.y) 
    } 

    func substractPointCoordinates(sub: CGPoint) -> CGPoint { 
     return CGPoint(x: self.x - sub.x, y: self.y - sub.y) 
    } 
} 

class ObyRectangleFeature : NSObject { 

    var topLeft: CGPoint! 
    var topRight: CGPoint! 
    var bottomLeft: CGPoint! 
    var bottomRight: CGPoint! 

    var centerPoint : CGPoint{ 
     get { 
      let centerX = ((topLeft.x + bottomLeft.x)/2 + (topRight.x + bottomRight.x)/2)/2 
      let centerY = ((topRight.y + topLeft.y)/2 + (bottomRight.y + bottomLeft.y)/2)/2 
      return CGPoint(x: centerX, y: centerY) 
     } 

    } 

    convenience init(_ rectangleFeature: CIRectangleFeature) { 
     self.init() 
     topLeft = rectangleFeature.topLeft 
     topRight = rectangleFeature.topRight 
     bottomLeft = rectangleFeature.bottomLeft 
     bottomRight = rectangleFeature.bottomRight 
    } 

    override init() { 
     super.init() 
    } 


    func rotate90Degree() -> Void { 

     let centerPoint = self.centerPoint 

//  /rotate cos(90)=0, sin(90)=1 
     topLeft = CGPoint(x: centerPoint.x + (topLeft.y - centerPoint.y), y: centerPoint.y + (topLeft.x - centerPoint.x)) 
     topRight = CGPoint(x: centerPoint.x + (topRight.y - centerPoint.y), y: centerPoint.y + (topRight.x - centerPoint.x)) 
     bottomLeft = CGPoint(x: centerPoint.x + (bottomLeft.y - centerPoint.y), y: centerPoint.y + (bottomLeft.x - centerPoint.x)) 
     bottomRight = CGPoint(x: centerPoint.x + (bottomRight.y - centerPoint.y), y: centerPoint.y + (bottomRight.x - centerPoint.x)) 
    } 

    func scaleRectWithCoeficient(ƒ_x: CGFloat, ƒ_y: CGFloat) -> Void { 
     topLeft = topLeft.scalePointByCeficient(ƒ_x, ƒ_y: ƒ_y) 
     topRight = topRight.scalePointByCeficient(ƒ_x, ƒ_y: ƒ_y) 
     bottomLeft = bottomLeft.scalePointByCeficient(ƒ_x, ƒ_y: ƒ_y) 
     bottomRight = bottomRight.scalePointByCeficient(ƒ_x, ƒ_y: ƒ_y) 
    } 

    func correctOriginPoints() -> Void { 

     let deltaCenter = self.centerPoint.reversePointCoordinates().substractPointCoordinates(self.centerPoint) 

     let TL = topLeft 
     let TR = topRight 
     let BL = bottomLeft 
     let BR = bottomRight 

     topLeft = BL.sumPointCoordinates(deltaCenter) 
     topRight = TL.sumPointCoordinates(deltaCenter) 
     bottomLeft = BR.sumPointCoordinates(deltaCenter) 
     bottomRight = TR.sumPointCoordinates(deltaCenter) 
    } 
} 

И это код инициализации:

let scalatedRect : ObyRectangleFeature = ObyRectangleFeature(rectangleFeature) 
     // fromSize -> Initial size of the CIImage 
     // toSize -> the size of the scaled Image 
     let ƒ_x = (fromSize.width/toSize.width) 
     let ƒ_y = (fromSize.height/toSize.height) 

     /*the coeficients are interchange intentionally cause of the different 
     coordinate system used by CIImage and UIImage, you could rotate before 
     scaling, to preserve the order, but if you do, the result will be offCenter*/ 

     scalatedRect.scaleRectWithCoeficient(ƒ_y, ƒ_y: ƒ_x) 
     scalatedRect.rotate90Degree() 
     scalatedRect.correctOriginPoints() 

На данный момент scaleRect готов к обращению любым способом.

+0

Могу ли я использовать toSize в качестве размера imageView? – John55

+0

да, размер изображенияView - это конечный размер изображения, при этом размер масштабируемого изображения – 4oby

+0

Oby еще раз спасибо за ответ. Мне было интересно, нужно ли мне перевернуть точки, чтобы вернуться к исходным пунктам, возможно ли это с помощью этого кода или это еще одна проблема? – John55

5

Если вам нужно отобразить только путь, вам будет проще рисовать путь в CAShapeLayer.

  1. Добавить CAShapeLayer в изображение предварительного просмотра.
  2. Рассчитать прямоугольник.
  3. Создайте UIBezierPath для этой функции.
  4. Преобразование пути в соответствии с исходным изображением.
  5. Установите путь к CAShapeLayer

Некоторые осложнения возникают на этапе 4, если вам необходимо поддерживать масштабируемые изображения или изображения с ориентацией (то есть что-то от камеры пользователя).

Ниже приведен пример. Это поддерживает Этот код предполагает, что изображение отображается в UIImageView с contentMode AspectFit, AspectFill, ScaleToFill или Center. Он также поддерживает изображения с ориентацией вверх, вниз, вправо и влево.

// Extension for calculating the image scale in an image view. 
// See: http://stackoverflow.com/questions/6856879/iphone-getting-the-size-of-an-image-after-aspectft 
extension UIImageView { 

    var imageScale: CGSize? { 

     guard let image = image else { 
      return nil 
     } 

     let sx = Double(self.frame.size.width/image.size.width) 
     let sy = Double(self.frame.size.height/image.size.height) 
     var s = 1.0 
     switch (self.contentMode) { 
     case .ScaleAspectFit: 
      s = fmin(sx, sy) 
      return CGSize (width: s, height: s) 

     case .ScaleAspectFill: 
      s = fmax(sx, sy) 
      return CGSize(width:s, height:s) 

     case .ScaleToFill: 
      return CGSize(width:sx, height:sy) 

     default: 
      return CGSize(width:s, height:s) 
     } 
    } 
} 

// Extension which provides a transform to rotate the image based on it's orientation metadata. 
extension UIImageView { 

    var normalizedTransformForOrientation: CGAffineTransform? { 

     guard let image = image else { 
      return nil 
     } 

     let r: CGFloat 

     switch image.imageOrientation { 

     case .Up: 
      r = 0 

     case .Down: 
      r = +1.0 

     case .Left: 
      r = -0.5 

     case .Right: 
      r = +0.5 

     default: 
      fatalError() 
     } 

     let cx = CGRectGetMidX(bounds) 
     let cy = CGRectGetMidY(bounds) 

     var transform = CGAffineTransformIdentity 
     transform = CGAffineTransformTranslate(transform, cx, cy) 
     transform = CGAffineTransformRotate(transform, CGFloat(M_PI) * r) 
     transform = CGAffineTransformTranslate(transform, -cx, -cy) 
     return transform 
    } 
} 

class ViewController: UIViewController { 

    // Shape layer for displaying the path. 
    let pathLayer: CAShapeLayer = { 
     let layer = CAShapeLayer() 
     layer.fillColor = UIColor.greenColor().colorWithAlphaComponent(0.3).CGColor 
     layer.strokeColor = UIColor.greenColor().colorWithAlphaComponent(0.9).CGColor 
     layer.lineWidth = 2.0 
     return layer 
    }() 

    // Image view where the preview and path overlay will be displayed. 
    @IBOutlet var imageView: UIImageView? 

    override func viewDidLoad() { 

     super.viewDidLoad() 

     // Add the path overlay to the image view. 
     imageView?.layer.addSublayer(pathLayer) 

     // Load a sample image from the assets. 
     selectImage(UIImage(named: "sample")) 
    } 

    func selectImage(image: UIImage?) { 

     imageView?.image = image 

     if let image = image { 
      processImage(image) 
     } 
    } 

    // Detect rectangles in image, and draw the path on the screen. 
    func processImage(input: UIImage) { 

     let path = pathsForRectanglesInImage(input) 

     let transform = pathTransformForImageView(imageView!) 
     path?.applyTransform(transform) 

     pathLayer.path = path?.CGPath 
    } 

    // Detect rectangles in an image and return a UIBezierPath. 
    func pathsForRectanglesInImage(input: UIImage) -> UIBezierPath? { 

     guard let sourceImage = CIImage(image: input) else { 
      return nil 
     } 

     let features = performRectangleDetection(sourceImage) 

     return pathForFeatures(features) 
    } 

    // Detect rectangles in image. 
    func performRectangleDetection(image: CIImage) -> [CIFeature] { 

     let detector:CIDetector = CIDetector(
      ofType: CIDetectorTypeRectangle, 
      context: nil, 
      options: [CIDetectorAccuracy : CIDetectorAccuracyHigh] 
     ) 

     let features = detector.featuresInImage(image) 

     return features 
    } 

    // Compose a UIBezierPath from CIRectangleFeatures. 
    func pathForFeatures(features: [CIFeature]) -> UIBezierPath { 

     let path = UIBezierPath() 

     for feature in features { 

      guard let rect = feature as? CIRectangleFeature else { 
       continue 
      } 

      path.moveToPoint(rect.topLeft) 
      path.addLineToPoint(rect.topRight) 
      path.addLineToPoint(rect.bottomRight) 
      path.addLineToPoint(rect.bottomLeft) 
      path.closePath() 
     } 

     return path 
    } 

    // Calculate the transform to orient the preview path to the image shown inside the image view. 
    func pathTransformForImageView(imageView: UIImageView) -> CGAffineTransform { 

     guard let image = imageView.image else { 
      return CGAffineTransformIdentity 
     } 

     guard let imageScale = imageView.imageScale else { 
      return CGAffineTransformIdentity 
     } 

     guard let imageTransform = imageView.normalizedTransformForOrientation else { 
      return CGAffineTransformIdentity 
     } 

     let frame = imageView.frame 

     let imageWidth = image.size.width * imageScale.width 
     let imageHeight = image.size.height * imageScale.height 

     var transform = CGAffineTransformIdentity 

     // Rotate to match the image orientation. 
     transform = CGAffineTransformConcat(imageTransform, transform) 

     // Flip vertically (flipped in CIDetector). 
     transform = CGAffineTransformTranslate(transform, 0, CGRectGetHeight(frame)) 
     transform = CGAffineTransformScale(transform, 1.0, -1.0) 

     // Centre align. 
     let tx: CGFloat = (CGRectGetWidth(frame) - imageWidth) * 0.5 
     let ty: CGFloat = (CGRectGetHeight(frame) - imageHeight) * 0.5 
     transform = CGAffineTransformTranslate(transform, tx, ty) 

     // Scale to match UIImageView scaling. 
     transform = CGAffineTransformScale(transform, imageScale.width, imageScale.height) 

     return transform 
    } 
} 

Detected rectangle with stroked overlay

+0

Эй Люк, Еще раз спасибо за ответ. Я копирую и вставляю ваш код точно, но путь безье по-прежнему отключен, поскольку он находится в нижней левой части представления, а не на изображении. Кроме этого, он масштабируется соответствующим образом. Кажется, я не могу понять это – John55

+0

@ John55 Я обновил ответ, чтобы выяснить, какие варианты использования поддерживаются. Если ваш вопрос охватывает другой вариант использования (например, другие ориентации, режим содержимого или любые другие условия), пожалуйста, добавьте это, и я обновлю свой ответ. –

+0

@ John55 Это произойдет, если масштаб исходного изображения больше 1. например, если он загружен из файла с расширением 2x. Как вы получаете изображение? –

2

Координаты прямоугольника, возвращаемые с CIDetector, относятся к CIImage, на котором он обнаруживает - это координаты изображения. Чтобы использовать эти координаты с UIBezierPath, нам нужно выполнить ряд преобразований.

Прежде всего, мы должны найти отношение кадра предварительного просмотра камеры к размеру CIImage, который использует CIRectangleFeature.

Затем, с этим соотношением, мы должны перевернуть скорректированные координаты, поскольку Core Image (CIImage) использует другую систему координат, чем Core Animation (CALayer/UIBezierPath).

Итак:

CGRect previewRect = self.frame; 
CGRect imageRect = image.extent; 

// find ratio between the video preview rect and the image rect; rectangle feature coordinates are relative to the CIImage 
CGFloat deltaX = CGRectGetWidth(previewRect)/CGRectGetWidth(imageRect); 
CGFloat deltaY = CGRectGetHeight(previewRect)/CGRectGetHeight(imageRect); 

// transform to UIKit coordinate system 
CGAffineTransform transform = CGAffineTransformMakeTranslation(0.f, CGRectGetHeight(previewRect)); 
transform = CGAffineTransformScale(transform, 1, -1); 
// apply preview to image scaling 
transform = CGAffineTransformScale(transform, deltaX, deltaY); 

CGPoint points[4]; 
points[0] = CGPointApplyAffineTransform(_borderDetectLastRectangleFeature.topLeft, transform); 
points[1] = CGPointApplyAffineTransform(_borderDetectLastRectangleFeature.topRight, transform); 
points[2] = CGPointApplyAffineTransform(_borderDetectLastRectangleFeature.bottomRight, transform); 
points[3] = CGPointApplyAffineTransform(_borderDetectLastRectangleFeature.bottomLeft, transform); 


UIBezierPath *path = [UIBezierPath new]; 
[path moveToPoint:points[0]]; 
[path addLineToPoint:points[1]]; 
[path addLineToPoint:points[2]]; 
[path addLineToPoint:points[3]]; 
[path addLineToPoint:points[0]]; 
[path closePath]; 

CAShapeLayer *_shapeLayer = [CAShapeLayer layer]; 
_shapeLayer.fillColor = [UIColor colorWithRed:.5 green:1 blue:.5 alpha:.6f].CGColor; 
_shapeLayer.strokeColor = [UIColor blackColor].CGColor; 
_shapeLayer.lineWidth = 2; 
_shapeLayer.path = path.CGPath; 
[self.layer addSublayer:_shapeLayer]; 
+0

Didnt Работал для меня – Hassy

+0

Ничего, это почти работает для меня, только как-то форма, которую я рисую, слишком мала как на левой, так и на правой стороне. – Peterdk

+0

Я отключил часть моего другого кода, который рисует оригинальную форму, и теперь он точно соответствует. Теперь, чтобы понять, как сделать дополнительное преобразование, чтобы я мог отображать его в прямом эфире на потоке камеры. Так что спасибо, это кажется хорошим решением! – Peterdk

Смежные вопросы