首页
学习
活动
专区
工具
TVP
发布
社区首页 >问答首页 >用UIBezierPath - Swift表示CIRectangleFeature

用UIBezierPath - Swift表示CIRectangleFeature
EN

Stack Overflow用户
提问于 2016-07-06 10:37:56
回答 2查看 3.8K关注 0票数 22

目前我正在使用CIDetector来检测我的UIImage中的矩形。我正在执行建议的方法,将坐标传递到一个过滤器中,以获得一个放在已获取的UIImage上的CIImage。它看起来是这样的:

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缩放它们。不幸的是,我不知道如何正确地做到这一点。我知道我可以重用我的一些检测代码来实现这一点,但我不知道采取正确的步骤。任何帮助都将不胜感激!谢谢。以下是正在发生的情况的一个示例:

更新

为了测试我在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)

更新

为了尝试和缩放我的坐标,我又尝试了两件事。

第一:我尝试过使用UIView的convertPoint函数,以便将点从图像转换为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)

然后,我用这些点返回了一个新的rect。

第二:我尝试了一个简单的坐标转换,基于图像和imageView的宽度和高度的差异。代码如下:

 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)

更新我也尝试过使用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)

没有一个是有效的。我不知道我还能尝试什么。请帮帮忙。我会非常感激的。谢谢!

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2016-07-11 15:40:45

几天来,我一直在努力解决同样的问题,以下是我如何克服这个问题的:

我创建了一个自定义类来存储点,并添加了一些辅助函数:

//
//  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已经准备好以您喜欢的任何方式绘制。

票数 3
EN

Stack Overflow用户

发布于 2016-07-10 07:39:28

如果您只需要显示路径,那么在CAShapeLayer中绘制路径会更容易一些。

为feature.

  • Transform添加路径以与源图像匹配的image.

  • Calculate rectangle.

  • Create a

  • CAShapeLayer到预览中。

  • 设置指向image.

  • Calculate的路径

如果您需要支持缩放图像或带有方向的图像(即来自用户相机的任何内容),则在步骤4中会出现一些复杂情况。

下面是一个例子。支持此代码的前提是图像显示在UIImageView中,contentMode为AspectFit、AspectFill、ScaleToFill或Centre。它还支持图像具有向上、向下、向右和向左的方向。

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

票数 7
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/38215593

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档