Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[PR] Add some control UI components for parameters of post-process #50

Merged
merged 8 commits into from
May 9, 2020
4 changes: 4 additions & 0 deletions PoseEstimation-TFLiteSwift.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
7138DCCF242142FE0048E1D2 /* TFLiteFlatArray.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7138DCCE242142FE0048E1D2 /* TFLiteFlatArray.swift */; };
71A1ED1F24527D55001F796C /* PoseConfidenceMapDrawingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71A1ED1E24527D55001F796C /* PoseConfidenceMapDrawingView.swift */; };
71A1ED4124574F2E001F796C /* StillImageHeatmapViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71A1ED4024574F2E001F796C /* StillImageHeatmapViewController.swift */; };
71B07B97245E5C6C001FD385 /* NumericExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71B07B96245E5C6C001FD385 /* NumericExtension.swift */; };
71DD577F2446D7CF0024C146 /* NonMaximumnonSuppression.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71DD577E2446D7CF0024C146 /* NonMaximumnonSuppression.swift */; };
71E8D9172438BAC10081DD6E /* openpose_ildoonet.tflite in Resources */ = {isa = PBXBuildFile; fileRef = 71E8D9162438BAC10081DD6E /* openpose_ildoonet.tflite */; };
71E8D9192438BAD80081DD6E /* OpenPosePoseEstimator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71E8D9182438BAD80081DD6E /* OpenPosePoseEstimator.swift */; };
Expand Down Expand Up @@ -65,6 +66,7 @@
7138DCCE242142FE0048E1D2 /* TFLiteFlatArray.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TFLiteFlatArray.swift; sourceTree = "<group>"; };
71A1ED1E24527D55001F796C /* PoseConfidenceMapDrawingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PoseConfidenceMapDrawingView.swift; sourceTree = "<group>"; };
71A1ED4024574F2E001F796C /* StillImageHeatmapViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StillImageHeatmapViewController.swift; sourceTree = "<group>"; };
71B07B96245E5C6C001FD385 /* NumericExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NumericExtension.swift; sourceTree = "<group>"; };
71DD577E2446D7CF0024C146 /* NonMaximumnonSuppression.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NonMaximumnonSuppression.swift; sourceTree = "<group>"; };
71E8D9162438BAC10081DD6E /* openpose_ildoonet.tflite */ = {isa = PBXFileReference; lastKnownFileType = file; path = openpose_ildoonet.tflite; sourceTree = "<group>"; };
71E8D9182438BAD80081DD6E /* OpenPosePoseEstimator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenPosePoseEstimator.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -138,6 +140,7 @@
7105C93B241E8CE3001A4325 /* CVPixelBufferExtension.swift */,
712A7FC52425FD7200B043F9 /* UIImageExtension.swift */,
7105C93D241E90C2001A4325 /* DataExtension.swift */,
71B07B96245E5C6C001FD385 /* NumericExtension.swift */,
);
name = Extension;
sourceTree = "<group>";
Expand Down Expand Up @@ -325,6 +328,7 @@
71A1ED1F24527D55001F796C /* PoseConfidenceMapDrawingView.swift in Sources */,
7105C93C241E8CE3001A4325 /* CVPixelBufferExtension.swift in Sources */,
712A7FC9242667C900B043F9 /* PEFMHourglassPoseEstimator.swift in Sources */,
71B07B97245E5C6C001FD385 /* NumericExtension.swift in Sources */,
7105C91A241CE9B6001A4325 /* LiveImageViewController.swift in Sources */,
7105C916241CE9B5001A4325 /* AppDelegate.swift in Sources */,
7105C92F241D0235001A4325 /* PoseEstimator.swift in Sources */,
Expand Down
993 changes: 897 additions & 96 deletions PoseEstimation-TFLiteSwift/Base.lproj/Main.storyboard

Large diffs are not rendered by default.

121 changes: 98 additions & 23 deletions PoseEstimation-TFLiteSwift/LiveImageViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,26 @@ class LiveImageViewController: UIViewController {
// MARK: - IBOutlets
@IBOutlet weak var previewView: UIView?
@IBOutlet weak var overlayLineDotView: PoseKeypointsDrawingView?
@IBOutlet weak var humanTypeSegment: UISegmentedControl?
@IBOutlet weak var dimensionSegment: UISegmentedControl?
@IBOutlet var partButtons: [UIButton]?
@IBOutlet weak var thresholdLabel: UILabel?
@IBOutlet weak var thresholdSlider: UISlider?
@IBOutlet weak var partThresholdLabel: UILabel?
@IBOutlet weak var partThresholdSlider: UISlider?
@IBOutlet weak var pairThresholdLabel: UILabel?
@IBOutlet weak var pairThresholdSlider: UISlider?
@IBOutlet weak var pairNMSFilterSizeLabel: UILabel?
@IBOutlet weak var pairNMSFilterSizeStepper: UIStepper?
@IBOutlet weak var humanMaxNumberLabel: UILabel?
@IBOutlet weak var humanMaxNumberStepper: UIStepper?

var overlayViewRelativeRect: CGRect = .zero
var pixelBufferWidth: CGFloat = 0

var isSinglePerson: Bool = true {
didSet {
humanTypeSegment?.selectedSegmentIndex = isSinglePerson ? 0 : 1
}
}
lazy var partIndexes: [String: Int] = {
var partIndexes: [String: Int] = [:]
poseEstimator.partNames.enumerated().forEach { offset, partName in
Expand All @@ -32,16 +46,58 @@ class LiveImageViewController: UIViewController {
guard let partName = selectedPartName.components(separatedBy: "(").first else { return nil }
return partIndexes[partName]
}
var threshold: Float? {
var partThreshold: Float? {
didSet {
guard let thresholdSlider = thresholdSlider else { return }
if let threshold = threshold {
thresholdSlider.value = threshold
} else {
thresholdSlider.value = thresholdSlider.minimumValue
let (slider, label, value) = (partThresholdSlider, partThresholdLabel, partThreshold)
if let slider = slider { slider.value = value ?? slider.minimumValue }
if let label = label { label.text = value.labelString }
}
}
var pairThreshold: Float? {
didSet {
let (slider, label, value) = (pairThresholdSlider, pairThresholdLabel, pairThreshold)
if let slider = slider { slider.value = value ?? slider.minimumValue }
if let label = label { label.text = value.labelString }
}
}
var pairNMSFilterSize: Int = 3 {
didSet {
let (stepper, label, value) = (pairNMSFilterSizeStepper, pairNMSFilterSizeLabel, pairNMSFilterSize)
if let stepper = stepper { stepper.value = Double(value) }
if let label = label { label.text = value.labelString }
}
}
var humanMaxNumber: Int? = 5 {
didSet {
let (stepper, label, value) = (humanMaxNumberStepper, humanMaxNumberLabel, humanMaxNumber)
if let stepper = stepper {
guard Int(stepper.minimumValue) != value else { humanMaxNumber = nil; return }
if let value = value { stepper.value = Double(value) }
else { stepper.value = stepper.minimumValue }
}
if let label = label { label.text = value.labelString }
}
}

var preprocessOptions: PreprocessOptions {
let scalingRatio = pixelBufferWidth / overlayViewRelativeRect.width
let targetAreaRect = overlayViewRelativeRect.scaled(to: scalingRatio)
return PreprocessOptions(cropArea: .customAspectFill(rect: targetAreaRect))
}
var humanType: PostprocessOptions.HumanType {
if isSinglePerson {
return .singlePerson
} else {
return .multiPerson(pairThreshold: pairThreshold,
nmsFilterSize: pairNMSFilterSize,
maxHumanNumber: humanMaxNumber)
}
}
var postprocessOptions: PostprocessOptions {
return PostprocessOptions(partThreshold: partThreshold,
bodyPart: selectedPartIndex,
humanType: humanType)
}

// MARK: - VideoCapture Properties
var videoCapture = VideoCapture()
Expand All @@ -59,7 +115,12 @@ class LiveImageViewController: UIViewController {
setUpUI()

// setup initial post-process params
threshold = 0.1 // initial threshold for part (not for pair)
isSinglePerson = true /// `multi-pose`
partThreshold = 0.1 ///
pairThreshold = 3.4 /// Only used on `multi-person` mode. Before sort edges by cost, filter by pairThreshold for performance
pairNMSFilterSize = 3 /// Only used on `multi-person` mode. If 3, real could be 7X7 filter // (3●2+1)X(3●2+1)
humanMaxNumber = nil /// Only used on `multi-person` mode. Not support yet

select(on: "ALL")
}

Expand Down Expand Up @@ -121,7 +182,7 @@ class LiveImageViewController: UIViewController {
button.addTarget(self, action: #selector(selectPart), for: .touchUpInside)
}

thresholdSlider?.isContinuous = false // `changeThreshold` will be called when touch up on slider
partThresholdSlider?.isContinuous = false // `changeThreshold` will be called when touch up on slider
}

override func viewDidLayoutSubviews() {
Expand Down Expand Up @@ -162,13 +223,28 @@ class LiveImageViewController: UIViewController {
updatePartButton(on: partName)
}

@IBAction func didChangedThresholdValue(_ sender: UISlider) {
threshold = (sender.value == sender.minimumValue) ? nil : sender.value
if let threshold = threshold {
thresholdLabel?.text = String(format: "%.2f", threshold)
} else {
thresholdLabel?.text = "nil"
}
@IBAction func didChangeHumanType(_ sender: UISegmentedControl) {
isSinglePerson = (sender.selectedSegmentIndex == 0)
}

@IBAction func didChangeDimension(_ sender: UISegmentedControl) {
// NOT SUPPORT YET
}

@IBAction func didChangedPartThreshold(_ sender: UISlider) {
partThreshold = (sender.value == sender.minimumValue) ? nil : sender.value
}

@IBAction func didChangePairThreshold(_ sender: UISlider) {
pairThreshold = (sender.value == sender.minimumValue) ? nil : sender.value
}

@IBAction func didChangePairNMSFilterSize(_ sender: UIStepper) {
pairNMSFilterSize = Int(sender.value)
}

@IBAction func didChangeHumanMaxNumber(_ sender: UIStepper) {
humanMaxNumber = (sender.value == sender.minimumValue) ? nil : Int(sender.value)
}
}

Expand All @@ -181,12 +257,11 @@ extension LiveImageViewController: VideoCaptureDelegate {

extension LiveImageViewController {
func inference(with pixelBuffer: CVPixelBuffer) {
let scalingRatio = pixelBuffer.size.width / overlayViewRelativeRect.width
let targetAreaRect = overlayViewRelativeRect.scaled(to: scalingRatio)
let partIndex: Int? = selectedPartIndex
let threshold: Float? = self.threshold
let input: PoseEstimationInput = .pixelBuffer(pixelBuffer: pixelBuffer, cropArea: .customAspectFill(rect: targetAreaRect))
let result: Result<PoseEstimationOutput, PoseEstimationError> = poseEstimator.inference(input, with: threshold, on: partIndex)
pixelBufferWidth = pixelBuffer.size.width
let input: PoseEstimationInput = .pixelBuffer(pixelBuffer: pixelBuffer,
preprocessOptions: preprocessOptions,
postprocessOptions: postprocessOptions)
let result: Result<PoseEstimationOutput, PoseEstimationError> = poseEstimator.inference(input)

switch (result) {
case .success(let output):
Expand Down
42 changes: 42 additions & 0 deletions PoseEstimation-TFLiteSwift/NumericExtension.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
//
// NumericExtension.swift
// PoseEstimation-TFLiteSwift
//
// Created by Doyoung Gwak on 2020/05/03.
// Copyright © 2020 Doyoung Gwak. All rights reserved.
//

import Foundation

extension Optional where Wrapped == Float {
var labelString: String {
guard let value = self else { return "nil" }
return String(format: "%.2f", value)
}
}

extension Float32 {
func string(_ format: String = "%.2f") -> String {
return String(format: format, self)
}
}

extension Optional where Wrapped == Float {
static func *(lhs: Wrapped?, rhs: Float) -> Self {
guard let lhs = lhs else { return nil }
return some(lhs * rhs)
}
}

extension Int {
var labelString: String {
return String(format: "%d", self)
}
}

extension Optional where Wrapped == Int {
var labelString: String {
guard let value = self else { return "nil" }
return value.labelString
}
}
Loading