使用 SwiftUI 显示多个 VNRecognizedObjectObservation 边界框时偏移错误

2023-12-11

我正在使用 Vision 来检测物体,然后得到[VNRecognizedObjectObservation]我在显示标准化矩形之前对其进行转换:

let transform = CGAffineTransform(scaleX: 1, y: -1).translatedBy(x: 0, y: -CGFloat(height))
VNImageRectForNormalizedRect(normalizedRect, width, height) // Displayed with SwiftUI, that's why I'm applying transform
    .applying(transform)

宽度和高度来自 SwiftUI GeometryReader:

Image(...)
    .resizable()
    .scaledToFit()
    .overlay {
        GeometryReader { geometry in // ZStack and ForEach([VNRecognizedObjectObservation], id: \.uuid), then:
            let calculatedRect = calculateRect(boundingBox, geometry)
            Rectangle()
                .frame(width: calculatedRect.width, height: calculatedRect.height)
                .offset(x: calculatedRect.origin.x, y: calculatedRect.origin.y)
        }
    }

但问题是,即使在方形图像上,许多框的位置也不正确(尽管有些是准确的)。

这与模型无关,因为当我在 Xcode 模型预览部分尝试相同的图像(使用相同的 MLModel)时,它们具有非常准确的 BB。

我的应用程序中的示例图像:

Sample Image on my App

Xcode 预览中的示例图像:

Sample Image in Xcode Preview


更新(最小可重现示例):

里面有这段代码ContentView.swift as a macOS SwiftUI项目同时拥有YOLOv3Tiny.ml模型在项目捆绑包中将产生相同的结果。

import SwiftUI
import Vision
import CoreML

class Detection: ObservableObject {
    let imgURL = URL(string: "https://i.imgur.com/EqsxxTc.jpg")! // Xcode preview generates this: https://i.imgur.com/6IPNQ8b.png
    @Published var objects: [VNRecognizedObjectObservation] = []

    func getModel() -> VNCoreMLModel? {
        if let modelURL = Bundle.main.url(forResource: "YOLOv3Tiny", withExtension: "mlmodelc") {
            if let mlModel = try? MLModel(contentsOf: modelURL, configuration: MLModelConfiguration()) {
                return try? VNCoreMLModel(for: mlModel)
            }
        }
        return nil
    }

    func detect() async {
        guard let model = getModel(), let tiff = NSImage(contentsOf: imgURL)?.tiffRepresentation else {
            fatalError("Either YOLOv3Tiny.mlmodel is not in project bundle, or image failed to load.")
            // YOLOv3Tiny: https://ml-assets.apple.com/coreml/models/Image/ObjectDetection/YOLOv3Tiny/YOLOv3Tiny.mlmodel
        }
        let request = VNCoreMLRequest(model: model) { (request, error) in
            DispatchQueue.main.async {
                self.objects = (request.results as? [VNRecognizedObjectObservation]) ?? []
            }
        }
        try? VNImageRequestHandler(data: tiff).perform([request])
    }

    func deNormalize(_ rect: CGRect, _ geometry: GeometryProxy) -> CGRect {
        let transform = CGAffineTransform(scaleX: 1, y: -1).translatedBy(x: 0, y: -CGFloat(geometry.size.height))
        return VNImageRectForNormalizedRect(rect, Int(geometry.size.width), Int(geometry.size.height)).applying(transform)
    }
}

struct ContentView: View {
    @StateObject var detection = Detection()

    var body: some View {
        AsyncImage(url: detection.imgURL) { img in
            img.resizable().scaledToFit().overlay {
                GeometryReader { geometry in
                    ZStack {
                        ForEach(detection.objects, id: \.uuid) { object in
                            let rect = detection.deNormalize(object.boundingBox, geometry)
                            Rectangle()
                                .stroke(lineWidth: 2)
                                .foregroundColor(.red)
                                .frame(width: rect.width, height: rect.height)
                                .offset(x: rect.origin.x, y: rect.origin.y)
                        }
                    }
                }
            }
        } placeholder: {
            ProgressView()
        }
        .onAppear {
            Task { await self.detection.detect() }
        }
    }
}

Edit:进一步的测试表明 VN 返回正确的位置,而我的deNormalize()函数还返回正确的位置和大小,因此它必须与 SwiftUI 相关。


Issue 1

GeometryReader使里面的所有东西都缩小到最小尺寸。

Add .border(Color.orange) to the ZStack你会看到类似我下面的内容。

enter image description here

您可以使用.frame(maxWidth: .infinity, maxHeight: .infinity)使ZStack拉伸以占据所有可用空间。

Issue 2

position vs offset.

offset通常从中心开始然后你offset按指定金额。

position更像是origin.

将此视图的中心定位在其父坐标空间中的指定坐标处。

Issue 3

调整中心位置与原点使用的左上角 (0, 0)。

Issue 4

The ZStack需要在X轴上翻转。

下面是完整的代码

import SwiftUI
import Vision
import CoreML
@MainActor
class Detection: ObservableObject {
    //Moved file to assets
    //let imgURL = URL(string: "https://i.imgur.com/EqsxxTc.jpg")! // Xcode preview generates this: https://i.imgur.com/6IPNQ8b.png
    let imageName: String = "EqsxxTc"
    @Published var objects: [VNRecognizedObjectObservation] = []
    
    func getModel() throws -> VNCoreMLModel {
        //Used model directly instead of loading from URL
        let model = try YOLOv3Tiny(configuration: .init()).model
        
        let mlModel = try VNCoreMLModel(for: model)
        
        return mlModel
    }
    
    func detect() async throws {
        let model = try getModel()
        
        guard let tiff = NSImage(named: imageName)?.tiffRepresentation else {
            // YOLOv3Tiny: https://ml-assets.apple.com/coreml/models/Image/ObjectDetection/YOLOv3Tiny/YOLOv3Tiny.mlmodel
            //fatalError("Either YOLOv3Tiny.mlmodel is not in project bundle, or image failed to load.")
            throw AppError.unableToLoadImage
        }
        //Completion handlers are not compatible with async/await you have to convert to a continuation.
        self.objects = try await withCheckedThrowingContinuation { (cont: CheckedContinuation<[VNRecognizedObjectObservation], Error>) in
            
            let request = VNCoreMLRequest(model: model) { (request, error) in
                if let error = error{
                    cont.resume(throwing: error)
                }else{
                    cont.resume(returning: (request.results as? [VNRecognizedObjectObservation]) ?? [])
                }
            }
            do{
                try VNImageRequestHandler(data: tiff).perform([request])
            }catch{
                cont.resume(throwing: error)
            }
        }
    }
    
    func deNormalize(_ rect: CGRect, _ geometry: GeometryProxy) -> CGRect {
        return VNImageRectForNormalizedRect(rect, Int(geometry.size.width), Int(geometry.size.height))
    }
}

struct ContentView: View {
    @StateObject var detection = Detection()
    
    var body: some View {
        Image(detection.imageName)
            .resizable()
            .scaledToFit()
            .overlay {
                GeometryReader { geometry in
                    ZStack {
                        ForEach(detection.objects, id: \.uuid) { object in
                            let rect = detection.deNormalize(object.boundingBox, geometry)
                            Rectangle()
                                .stroke(lineWidth: 2)
                                .foregroundColor(.red)
                                .frame(width: rect.width, height: rect.height)
                            //Changed to position
                            //Adjusting for center vs leading origin
                                .position(x: rect.origin.x + rect.width/2, y: rect.origin.y + rect.height/2)
                        }
                    }
                    //Geometry reader makes the view shrink to its smallest size
                    .frame(maxWidth: .infinity, maxHeight: .infinity)
                    //Flip upside down
                    .rotation3DEffect(.degrees(180), axis: (x: 1, y: 0, z: 0))
                    
                }.border(Color.orange)
            }
        
            .task {
                do{
                    try await self.detection.detect()
                }catch{
                    //Always throw errors to the View so you can tell the user somehow. You don't want crashes or to leave the user waiting for something that has failed.
                    print(error)
                }
            }
    }
}
struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

enum AppError: LocalizedError{
    case cannotFindFile
    case unableToLoadImage
}

enter image description here

正如您所注意到的,我还更改了其他一些内容,代码中有注释。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

使用 SwiftUI 显示多个 VNRecognizedObjectObservation 边界框时偏移错误 的相关文章

随机推荐

  • 为什么 Python 中的列表理解比 map() 更快?

    我正在研究Python中类似循环结构的性能问题 发现以下语句 除了列表推导式的语法优势之外 它们通常还 与同等使用地图一样快或更快 性能技巧 列表推导式的运行速度比等效的 for 循环要快一些 除非 你只会丢弃结果 蟒蛇速度 我想知道幕后的
  • Docker compose mysql 连接失败

    我正在尝试使用 docker compose 运行 2 个 docker 容器并将 mysql 容器连接到应用程序容器 Mysql 容器正在运行 但应用程序容器无法启动并出现错误错误 2003 无法连接到 127 0 0 1 3306 上的
  • 如何删除二维向量中的列,C++

    如果我在创建矩阵的向量中有一个向量 如何删除该矩阵中的特定列 我已经填充了二维向量 现在我需要一种方法来删除该向量中的特定列 例如我的向量看起来像 vector
  • Python:与 urljoin 的混淆

    我正在尝试从不同的部分形成 URL 但无法理解此方法的行为 例如 Python 3 x from urllib parse import urljoin gt gt gt urljoin some thing thing gt gt gt
  • `(Integer a) => a -> Bool` 和 `Integer -> Bool` 之间的区别?

    今天我用 Haskell 写了第一个程序 编译并运行成功 而且由于它不是典型的 你好世界 程序 它实际上做的远不止这些 所以请祝贺我 D 不管怎样 我对我的代码和 Haskell 中的语法没有什么疑问 Problem 我的程序读取一个整数N
  • 使用 Java 创建 Snake

    我决定使用 Java 重新创建 Snake 但我有点卡住了 目前 我有一个正方形 用户可以使用箭头键在屏幕上移动 当您按一次向左键时 方块开始使用计时器向左移动 您不需要按住该键或一直按住它 当您按下任何其他设置的键 右 上 下 时 它会改
  • 如何在 onitemclick 上的 webview 中加载 HTML 文件

    我有超过 100 个 html 文件 我希望每个文件在列表视图中单击行时打开 并且每个 html 文件应在 web 视图中打开 我尝试了此代码 但这不起作用 只有 web 视图在行单击时打开 html 文件没有出现 package com
  • Bootstrap 3 - 移动设备上的桌面视图

    在移动设备上时是否可以将引导网站显示为桌面版本 基本上 该页面将显示 992px 或 1200px 视口 而不是小型设备视口 例如 BBC允许您使用页面底部的链接在移动网站和桌面网站之间切换 这就是我想要做的 谢谢 利亚姆 您只需要设置视口
  • WooCommerce 中特定产品变体的自定义后缀

    我正在尝试为可变商品的价格添加后缀 它只需要在选择该特定项目时显示 我尝试了 stackoverflow 中的一些不同代码 但所有代码都将后缀添加到所有变量 而不仅仅是我需要的变量 目前我正在使用以下代码 但它给出了一个严重错误 致命错误
  • C# 和正则表达式 - 无法识别的分组构造

    目前正在研究客户端 服务器应用程序的论文 如果服务器接收到这样的信息 我遇到了一个障碍 ProToCooL unknown DESKTOP 29COFES 10 20 9 53 Hewlett Packard 179C PCWJA001X3
  • 匹配带有空格的不区分大小写的精确短语

    如果我有一个字符串 Hello I went to the store today 我有一系列比赛 perfectMatches array i went store today 它应该与这两者相匹配 数组可能会变得很大 所以我更愿意在 1
  • 如何在java中执行1个命令x次

    我想问一下如何多次执行1个命令 例如这段代码 System out println Hello World 我想运行 500 次 我该怎么做 谢谢 问候 威廉姆斯 使用 Java 8 Streams 你可以这样做 IntStream ran
  • 在 Objective-C 中将 NSArray 转换为 NSString

    我想知道如何转换NSArray Apple Pear 323 Orange 到一个字符串Objective C NSString result array valueForKey description componentsJoinedBy
  • Task == 是懒惰的吗?

    public Data GetCurrent Credentials credentials var data new Lazy
  • 无法领取 PosPrinter

    我有一台 TM T20 Epson 我正在使用此代码尝试打印 Hello Printer 消息 并且我不断跟踪 PosPrinter 的一些特性 public void ImprintHelloPrinter The Explorer Po
  • 从 ViewModel 打开一个窗口

    我试图使我的 WPF 应用程序解耦 因此我做了类似的事情 视图的项目 ViewModel 的项目 In the mainwindow我在按钮和按钮之间进行了绑定OpenChildWindowCommand它位于 ViewModel DLL
  • PHP如何执行命令

    我正在尝试使用 LibreOffice 将电子表格转换为另一种格式 当我从控制台执行命令时 它工作正常 但当我使用 exec 或 system 从 PHP 执行命令时 它不起作用 它没有显示任何错误或任何东西 它只是默默地失败 如果我尝试执
  • 如何公开用户控件内的 GridView 控件的列集合

    请参阅编辑 我希望能够在使用用户控件的 aspx 中执行此操作
  • NetSuite 保存搜索公式以将其他两列的结果相乘

    我当前有一个保存的搜索 用于填充项目列表 我当前的结果是标准 NetSuite 字段 即 名称 描述 类型 平均成本 和 可用 我正在尝试为公式添加另一列 该公式将平均成本乘以可用成本 得出可用 SOH 的值 在保存的搜索结果中添加一个新的
  • 使用 SwiftUI 显示多个 VNRecognizedObjectObservation 边界框时偏移错误

    我正在使用 Vision 来检测物体 然后得到 VNRecognizedObjectObservation 我在显示标准化矩形之前对其进行转换 let transform CGAffineTransform scaleX 1 y 1 tra