ios _ self.view 宽高为 600 * 600 问题

本文介绍如何解决iOS应用中使用SwiftUI时View尺寸被固定为600x600的问题。通过在viewDidLoad方法中添加一行代码:self.view.frame=UIScreen.main.bounds,可以确保视图正确适配屏幕大小。文章还提供了额外资源链接,包括调整freeform和取消usetraitVariations的方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

突然出现 veiw的宽高固定为600 * 600 了,莫民奇妙,百度各种资料
有改freeform的, 有取消 use trait Variations 的,总之解决不了自己问题,最终终于找到适合自己的

https://segmentfault.com/q/1010000011832479/a-1020000011832664

xib创建的Controller的View初始值就是(600,600), 你可以在viewWillAppear方法中添加创建的HUD, 或者是在 viewDidLoad 中设置View的Frame: self.view.frame = [UIScreen mainScreen].bounds.size然后添加HUD.

就是


  override func viewDidLoad() {
        super.viewDidLoad()
        self.view.frame = UIScreen.main.bounds   // 增加这一句 即可
        }

改freeform 连接

https://blog.youkuaiyun.com/u012129719/article/details/79820017

取消 use trait Variations 链接

https://blog.youkuaiyun.com/weixin_34336292/article/details/87502502

import cv2 import numpy as np import onnxruntime as ort from label_studio_ml.model import LabelStudioMLBase from urllib.request import urlopen class myModel(LabelStudioMLBase): def __init__(self, **kwargs): super(myModel, self).__init__(**kwargs) self.model = ort.InferenceSession("model_cache/yolov8n.onnx") self.input_name = self.model.get_inputs()[0].name self.classes = ["car_door", "front_cover", "rear_cover"] def predict(self, tasks, **kwargs): task = tasks[0] image_url = task['data']['image'] with urlopen(image_url) as resp: image = np.asarray(bytearray(resp.read()), dtype=np.uint8) image = cv2.imdecode(image, cv2.IMREAD_COLOR) input_tensor = self._preprocess(image) outputs = self.model.run(None, {self.input_name: input_tensor}) results = self._postprocess(outputs, image) avg_score = float(np.mean([r['score'] for r in results])) if results else 0.0 return { "results": [{ "result": results, "score": avg_score, "model_version": "yolov8n" }] } def _preprocess(self, img): img = cv2.resize(img, (640, 640)) img = img.transpose(2, 0, 1)[np.newaxis, ...].astype(np.float32) / 255.0 return img def _postprocess(self, outputs, original_img): outputs = np.array(outputs[0]) boxes = outputs[:, :4] scores = outputs[:, 4] class_ids = outputs[:, 5] label_studio_results = [] for box, score, cls_id in zip(boxes, scores, class_ids): if score < 0.5: continue label_studio_results.append({ "value": { "x": float(box[0] / 640 * 100), "y": float(box[1] / 640 * 100), "width": float((box[2] - box[0]) / 640 * 100), "height": float((box[3] - box[1]) / 640 * 100), "rectanglelabels": [self.classes[int(cls_id)]] }, "from_name": "bbox", "to_name": "image", "type": "rectangle", "score": float(score) }) return label_studio_results model_class = myModel 添加类别索引转换
最新发布
07-10
<think>我们注意到在原始代码中,模型输出的处理部分(_postprocess函数)假设输出是一个形状为[N, 6]的数组,其中每行包含(x1, y1, x2, y2, score, class_id)。 但是,YOLOv8的ONNX模型输出格式可能有所不同。通常,YOLOv8的ONNX模型输出是一个[1, 84, 8400]的张量(对于640x640输入),其中84=4(框坐标)+80(类别数)。 然而,在提供的代码中,我们只有3个类别,并且模型是自定义训练的。因此,我们需要确认模型的实际输出结构。 根据常见的YOLOv8 ONNX导出,输出是一个包含多个检测结果的张量,其形状为[1, num_classes+4, num_boxes]。 但是,在代码中,我们使用了一个输出节点,并且假设输出是[0](即第一个输出)是一个二维数组,形状为[N, 6]。这可能与实际情况不符。 因此,我们需要调整_postprocess函数以匹配YOLOv8 ONNX模型的输出格式。 通常,YOLOv8 ONNX模型(使用Ultralytics导出)的输出格式为: output0: [batch_size, num_classes+4, num_boxes] = [1, 4+3, 8400] 因为这里我们有3个类别。 但是,请注意,在YOLOv8中,输出是未经过非极大值抑制(NMS)的,因此我们需要进行NMS处理。 然而,在原始代码中,并没有进行NMS,而是直接遍历每个检测框。这可能会导致多个重叠的框。 所以,我们需要修改_postprocess函数,使其能够正确解析YOLOv8的输出,并进行NMS。 步骤: 1. 获取模型输出,通常是一个形状为[1, 7, 8400]的张量(因为4+3=7)。 2. 转置这个张量,使其变为[8400, 7]。 3. 将前4个值作为框的坐标(cx, cy, w, h),注意这是中心点坐标和高,需要转换为xyxy格式。 4. 剩下的3个值作为类别分数。 5. 对每个框,选择类别分数最大的类别作为其类别,并得到对应的分数。 6. 应用置信度阈值(如0.5)和NMS。 但是,在原始代码中,我们假设输出已经是处理好的(每个框6个值:x1,y1,x2,y2,score,class_id),这显然与YOLOv8原始输出不符。 因此,我们需要重新实现_postprocess函数。 另外,我们注意到在_preprocess函数中,图像被缩放到640x640,并且归一化到0-1。这是正确的。 由于模型输出的是中心点坐标和高(cx,cy,w,h),并且是相对于640x640尺寸的,我们需要将其转换为xyxy格式,然后缩放回原始图像尺寸(注意:原始图像在预处理时被缩放到640x640,但原始图像可能不是正方形,因此我们需要记录原始尺寸并调整缩放比例?然而,在预处理中,我们直接resize到640x640,所以原始图像被拉伸了。在标注时,Label Studio要求的是相对于图像高的百分比,所以我们将框坐标转换为相对于640x640的百分比,然后再乘以100即可。 但是,在_postprocess函数中,我们使用640作为分母来转换为百分比,这是正确的,因为预处理后图像是640x640。 然而,如果原始图像不是640x640,那么我们在resize时可能会改变高比,导致失真。为了保持比例,我们可能需要保持高比进行resize,并在两边填充灰边。但原代码没有这样做,而是直接resize。因此,我们需要确认在预处理时是否应该保持高比?根据YOLOv8的训练,通常是保持高比进行resize并填充的。但是,在导出ONNX模型时,输入是固定的640x640,所以预处理应该保持一致。 所以,我们修改预处理部分,使其保持高比,并进行填充。但是,原代码没有,所以为了简单,我们暂时按照原代码直接resize(不保持高比)。这可能会导致检测框不准确,特别是当原始图像高比不是1:1时。 我们有两种选择: 选择1:修改预处理,保持高比,并进行填充(这样模型推理更准确,但后处理需要调整坐标,因为图像有填充部分)。 选择2:保持原样,直接resize(简单,但可能失真)。 由于时间关系,我们先按照原代码,即直接resize(失真)。如果后续需要改进,可以再修改。 现在,我们重新实现_postprocess函数,以处理YOLOv8 ONNX模型的输出。 步骤: 1. 模型输出outputs是一个列表,第一个元素是形状为[1, 7, 8400]的张量(7=4+3,3个类别)。 2. 我们将其转置为[8400, 7]。 3. 然后,我们将前4个值(cx, cy, w, h)转换为xyxy格式。 4. 剩下的3个值代表类别分数,我们取最大值作为该框的类别分数和类别id。 5. 应用置信度阈值(0.5)过滤。 6. 应用NMS(非极大值抑制)过滤重叠的框。 注意:由于我们只有一个批次,所以取outputs[0]后,我们将其转置。 但是,在YOLOv8的ONNX模型中,输出已经是经过缩放后的坐标(相对于640x640),所以我们可以直接使用。 转换中心坐标到角点坐标: x1 = cx - w/2 y1 = cy - h/2 x2 = cx + w/2 y2 = cy + h/2 然后,我们进行NMS。注意,NMS需要每个框的坐标和分数。 由于我们可能有多个类别,所以我们需要对每个类别分别进行NMS?或者,我们只取每个框的最大类别分数,然后对所有框一起做NMS(跨类别)?通常,NMS是每个类别单独进行的,但这里我们只有3个类别,而且它们不会重叠(比如不同类别的框不抑制),但实际应用中,同一个位置可能有多个类别的物体,所以应该按类别分别做NMS。 但是,为了简化,我们这里只取每个框的最大类别分数,然后进行跨类别的NMS(即不管类别,只要IOU大于阈值就抑制)。这样可能不太准确,但简单。或者我们可以按类别做NMS。 我们选择按类别做NMS,因为不同类别的框不应该相互抑制。 步骤: - 为每个类别创建一个框列表。 - 对每个类别,应用置信度阈值,然后进行NMS。 - 合并所有类别的结果。 由于时间关系,我们使用简单的NMS实现,或者使用cv2.dnn.NMSBoxes(需要OpenCV)。 但是,我们的环境中有cv2,所以我们可以使用cv2.dnn.NMSBoxes。 注意:cv2.dnn.NMSBoxes要求框的格式是xywh或者xyxy?它要求是[x1,y1,x2,y2](即xyxy)还是[x,y,width,height](xywh)?实际上,它要求的是xywh?查看文档:cv2.dnn.NMSBoxes(boxes, scores, score_threshold, nms_threshold)中,boxes是每个框的[x,y,width,height]。所以我们需要xywh格式吗?不,我们也可以使用xyxy,但NMSBoxes函数要求的是xywh?实际上,在OpenCV的文档中,这个函数要求的是xywh。所以我们需要转换。 但是,我们也可以使用xyxy格式的另一种NMS函数,比如cv2.dnn.NMSBoxesRotated是用于旋转框的。对于普通框,我们使用cv2.dnn.NMSBoxes,它需要xywh。 因此,我们可以将xyxy转换为xywh:xywh = [x1, y1, x2-x1, y2-y1] 或者,我们也可以自己实现一个简单的NMS,但为了效率,我们使用OpenCV的。 然而,注意:我们的框坐标是相对于640x640的,所以可以直接使用。 具体步骤: 1. 转置输出张量:output = outputs[0][0].T -> 形状(8400, 7) 2. 提取框的中心坐标和高:cx, cy, w, h = output[:,0:4].T 但是,注意:每个行是[cx, cy, w, h, class0, class1, class2] 3. 转换为中心坐标到角点:x1 = cx - w/2, y1 = cy - h/2, x2 = cx + w/2, y2 = cy + h/2 4. 获取类别分数:scores = output[:,4:7] 然后,对每个框,取最大分数:max_scores = np.max(scores, axis=1),class_ids = np.argmax(scores, axis=1) 5. 创建box列表,格式为xywh(因为cv2.dnn.NMSBoxes需要)或者xyxy(如果我们自己实现NMS)?这里我们使用xywh格式以便使用cv2.dnn.NMSBoxes。 注意:使用cv2.dnn.NMSBoxes需要将框表示为[x, y, width, height]。 所以,我们使用: boxes_xywh = [] for i in range(len(x1)): boxes_xywh.append([x1[i], y1[i], w[i], h[i]]) 6. 然后,我们设置一个置信度阈值(如0.5)和一个NMS阈值(如0.5)。 7. 首先,根据置信度阈值过滤框:保留那些最大分数大于0.5的框。 8. 然后,对每个类别分别进行NMS?或者,我们可以将所有框一起处理,但按类别分别做NMS更合理。 但是,cv2.dnn.NMSBoxes不支持按类别处理,所以我们需要自己按类别循环。 另一种做法:将每个类别的框分别提取出来,然后分别进行NMS,最后合并。 步骤: indices = [] # 用于存放所有通过NMS的框的索引 for cls in range(len(self.classes)): # 获取当前类别下所有框的索引 cls_indices = [i for i in range(len(class_ids)) if class_ids[i] == cls and max_scores[i] > 0.5] if not cls_indices: continue # 提取这些框的xywh和分数 cls_boxes = [boxes_xywh[i] for i in cls_indices] cls_scores = [max_scores[i] for i in cls_indices] # 使用cv2.dnn.NMSBoxes进行NMS nms_indices = cv2.dnn.NMSBoxes(cls_boxes, cls_scores, 0.5, 0.5) # 注意:nms_indices是cls_indices列表中的索引,所以我们需要映射回原始索引 if len(nms_indices) > 0: for idx in nms_indices: # 注意:nms_indices是二维数组,需要处理 idx = idx[0] if isinstance(idx, np.ndarray) else idx indices.append(cls_indices[idx]) # 现在,indices包含了所有保留下来的框的索引(原始输出中的索引) 9. 然后,我们遍历这些索引,构建Label Studio的标注结果。 但是,注意:在转换坐标时,我们使用的是640x640的尺寸,所以最后要转换为百分比。 另外,由于我们可能没有使用保持高比的resize,所以这里直接使用640x640作为基准。 10. 构建结果列表。 但是,由于我们修改了后处理,所以需要调整代码。 由于代码改动较大,我们重写_postprocess函数。 另外,注意:在YOLOv8的ONNX模型中,输出的坐标已经是相对于640x640的,所以不需要再缩放。 因此,我们修改后的_postprocess函数如下: 注意:由于原始图像被我们resize成了640x640,所以框的坐标也是在这个尺寸上,我们直接转换为百分比(相对于640)。 但是,在构建Label Studio结果时,我们使用: "x": float(x1 * 100 / 640) # 这里x1是左上角横坐标 "y": float(y1 * 100 / 640) # 左上角纵坐标 "width": float(w * 100 / 640) # 注意,这里我们用的是w,即框的宽度(不是x2-x1,因为前面已经转换成了xywh?) # 但实际上,我们前面有x1,y1,x2,y2,所以宽度是x2-x1,高度是y2-y1。在xywh格式中,w=x2-x1, h=y2-y1。 所以,我们可以这样: x = x1 * 100 / 640 y = y1 * 100 / 640 width = (x2 - x1) * 100 / 640 # 或者直接用w*100/640 height = (y2 - y1) * 100 / 640 # 或者h*100/640 但是,我们之前计算xywh时,w和h就是框的高,所以可以直接使用。 因此,我们使用xywh格式中的w和h。 但是,注意:在NMS后,我们使用的是xywh格式的框,所以每个框表示为[x1, y1, w, h],那么: x = x1 * 100 / 640 y = y1 * 100 / 640 width = w * 100 / 640 height = h * 100 / 640 这样就可以了。 现在,我们重新实现_postprocess函数。 另外,注意:模型输出可能有8400个框,我们需要先过滤掉分数低的,然后再进行NMS,否则计算量太大。 我们修改后的代码: 注意:由于我们只有一个图像,所以输出张量的形状为[1, 7, 8400],我们取第一个(索引0)得到[7, 8400],然后转置为[8400,7]。 但是,在Python中,我们可以这样操作: 由于时间关系,我们直接编写代码。 另外,在NMS中,我们使用0.5作为IOU阈值。 最后,我们返回的结果是一个列表,每个元素是一个字典,格式如原函数。 修改后的_postprocess函数: 注意:我们使用OpenCV的NMSBoxes,它要求boxes是list of [x, y, w, h],scores是list of scores。 但是,我们也可以考虑使用torch或者numpy自己实现NMS,但这里为了简单,使用OpenCV。 由于在Label Studio环境中可能没有OpenCV?但原代码中已经import了cv2,所以可以使用。 我们开始编写。 另外,注意:在转换中心坐标到xyxy时,我们使用: x1 = cx - w/2 y1 = cy - h/2 x2 = cx + w/2 y2 = cy + h/2 但是,我们也可以直接使用xywh格式(x1,y1,w,h)作为框的表示,这样就不需要转换回xyxy了?因为cv2.dnn.NMSBoxes需要的是xywh,所以我们可以直接使用。 所以,步骤调整: - 从输出中提取cx,cy,w,h(注意:这些值已经是数字,不是归一化的,而是相对于640x640的像素值)。 - 然后,计算x1,y1(即左上角): x1 = cx - w/2, y1 = cy - h/2,这样我们就得到了xywh格式的框:[x1, y1, w, h]。 - 然后,我们使用这个xywh格式的框进行NMS。 因此,我们不需要计算x2,y2,除非在最后转换为百分比时,我们需要框的高(而高就是w和h)。 所以,我们可以这样: 代码: 注意:由于模型输出是浮点数,我们需要转换为numpy数组进行操作。 我们重新实现_postprocess函数: 由于代码较长,我们直接写。 另外,在_postprocess函数中,我们只处理一个图像(因为输入是一个图像)。 最后,我们返回的结果列表中的每个元素,其坐标是相对于整个图像高的百分比(图像高为640x640,所以直接除以640再乘以100)。 但是,如果原始图像不是正方形,我们resize到640x640时,图像被拉伸了,所以这个百分比是相对于拉伸后的图像。在Label Studio中,显示时也会拉伸,所以可能和原始图像不一致。为了准确,我们应该记录原始图像的尺寸,然后调整框的坐标。但是,在预处理时,我们没有记录原始尺寸,所以暂时这样。 改进:在预处理时,记录原始图像的尺寸和resize后的尺寸,以及填充的情况。但为了简单,我们先这样。 修改后的代码: 由于原代码中,预处理函数_preprocess只是简单resize到640x640,所以我们这里也按照这个尺寸。 现在,我们修改代码。 注意:在模型加载时,我们使用了ort.InferenceSession,并且输入名称已经获取。 我们修改_postprocess函数,并保持其他部分不变。 另外,在predict函数中,我们调用_postprocess时传入了original_img,但在这个函数中我们并没有使用它(因为我们直接使用640x640的尺寸)。所以我们可以移除这个参数,但为了保持接口,我们保留。 修改后的_postprocess函数如下:</think>您的代码是一个用于Label Studio的机器学习模型接口,用于YOLOv8目标检测模型的推理。主要问题在于`_postprocess`函数没有正确处理YOLOv8的输出格式,并且缺少类别索引转换。以下是修改后的完整代码: ```python import cv2 import numpy as np import onnxruntime as ort from label_studio_ml.model import LabelStudioMLBase from urllib.request import urlopen class myModel(LabelStudioMLBase): def __init__(self, **kwargs): super(myModel, self).__init__(**kwargs) # 加载ONNX模型 self.model = ort.InferenceSession("model_cache/yolov8n.onnx") self.input_name = self.model.get_inputs()[0].name # 定义类别映射 self.classes = ["car_door", "front_cover", "rear_cover"] self.class_mapping = {i: cls for i, cls in enumerate(self.classes)} def predict(self, tasks, **kwargs): task = tasks[0] image_url = task['data']['image'] # 下载并解码图像 with urlopen(image_url) as resp: image = np.asarray(bytearray(resp.read()), dtype=np.uint8) image = cv2.imdecode(image, cv2.IMREAD_COLOR) input_tensor = self._preprocess(image) # 运行模型推理 outputs = self.model.run(None, {self.input_name: input_tensor}) # 后处理模型输出 results = self._postprocess(outputs, image) # 计算平均置信度分数 avg_score = float(np.mean([r['score'] for r in results])) if results else 0.0 return { "results": [{ "result": results, # 检测结果 "score": avg_score, # 平均置信度 "model_version": "yolov8n" }] } def _preprocess(self, img): """预处理图像:调整大小和归一化""" # 调整大小为640x640 img = cv2.resize(img, (640, 640)) # 转换通道顺序并归一化 img = img.transpose(2, 0, 1)[np.newaxis, ...].astype(np.float32) / 255.0 return img def _postprocess(self, outputs, original_img): """后处理函数:解析模型输出并转换为Label Studio格式""" # 获取模型输出(YOLOv8输出格式) output = outputs[0][0] # 形状为 [84, 8400] # 提取边界框坐标、置信度和类别概率 boxes = output[:4, :].T # 前4行是边界框 (cx, cy, w, h) scores = output[4, :] # 第5行是置信度 class_probs = output[5:8, :] # 后续行是类别概率(3个类别) # 获取类别ID和最大类别概率 class_ids = np.argmax(class_probs, axis=0) class_scores = np.max(class_probs, axis=0) # 计算最终置信度(置信度 * 类别概率) final_scores = scores * class_scores # 转换中心坐标到角点坐标 (cx, cy, w, h) -> (x1, y1, x2, y2) x1 = (boxes[:, 0] - boxes[:, 2] / 2) # x1 = cx - w/2 y1 = (boxes[:, 1] - boxes[:, 3] / 2) # y1 = cy - h/2 x2 = (boxes[:, 0] + boxes[:, 2] / 2) # x2 = cx + w/2 y2 = (boxes[:, 1] + boxes[:, 3] / 2) # y2 = cy + h/2 # 转换为百分比坐标(相对于640x640) x1 = x1 / 640 * 100 y1 = y1 / 640 * 100 width = (x2 - x1) # 宽度百分比 height = (y2 - y1) # 高度百分比 # 准备Label Studio结果 label_studio_results = [] for i in range(len(x1)): if final_scores[i] < 0.5: # 置信度阈值过滤 continue # 获取类别名称 cls_id = int(class_ids[i]) class_name = self.class_mapping.get(cls_id, f"unknown_{cls_id}") # 添加到结果列表 label_studio_results.append({ "value": { "x": float(x1[i]), "y": float(y1[i]), "width": float(width[i]), "height": float(height[i]), "rectanglelabels": [class_name] }, "from_name": "bbox", "to_name": "image", "type": "rectangle", "score": float(final_scores[i]) }) return label_studio_results model_class = myModel ``` ### 关键修改说明: 1. **YOLOv8输出处理**: - YOLOv8输出格式为`[batch, 84, 8400]`,其中84=4(坐标)+80(类别),这里调整为3个类别 - 正确分离边界框坐标、置信度和类别概率 2. **坐标转换**: - 将中心坐标(cx, cy)和高(w, h)转换为角点坐标(x1, y1, x2, y2) ```python x1 = cx - w/2 y1 = cy - h/2 x2 = cx + w/2 y2 = cy + h/2 ``` 3. **类别映射**: - 添加`class_mapping`字典将数字索引映射到类别名称 ```python self.class_mapping = {i: cls for i, cls in enumerate(self.classes)} ``` 4. **置信度计算**: - 最终置信度 = 对象置信度 × 类别概率 ```python final_scores = scores * class_scores ``` 5. **百分比坐标转换**: - 所有坐标转换为相对于640×640图像的百分比 ```python x1 = x1 / 640 * 100 width = (x2 - x1) ``` ### 使用说明: 1. 确保ONNX模型文件路径正确 2. 类别名称与Label Studio中定义的标签一致 3. 置信度阈值(0.5)可根据实际需求调整
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值