22

現在、UIImage の四角形を検出するために CIDetector を使用しています。座標をフィルターに渡してCIImageを取得し、取得したUIImageの上に置くという提案された方法を実行しています。次のようになります。

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)

アップデート

座標を試してスケーリングするために、さらに2つのことを試しました。

番号 1: ポイントを画像から UIImageView に変換するために、UIView convertPoint 関数を使用しようとしました。コーディング方法は次のとおりです。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 を返しました。

その2:画像と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)

どれも機能していません。他に何を試すことができるかわかりません。助けてください。それは大歓迎です。ありがとう!

4

2 に答える 2

7

パスのみを表示する必要がある場合は、CAShapeLayer でパスを描画する方が少し簡単です。

  1. プレビュー イメージに CAShapeLayer を追加します。
  2. 長方形を計算します。
  3. 機能の UIBezierPath を作成します。
  4. ソース画像と一致するようにパスを変換します。
  5. CAShapeLayer へのパスを設定します

スケーリングされた画像、または向きのある画像 (つまり、ユーザーのカメラからのもの) をサポートする必要がある場合、ステップ 4 でいくつかの複雑さが生じます。

以下は例です。このコードは、AspectFit、AspectFill、ScaleToFill、または Centre の contentMode を使用して、画像が UIImageView に表示されることを前提としています。また、向きが上、下、右、左の画像もサポートしています。

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

ストローク オーバーレイで検出された四角形

于 2016-07-09T23:39:28.403 に答える
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で、好きな方法で描画する準備が整いました。

于 2016-07-11T07:40:45.210 に答える