guqing 2 日 前
コミット
ef93caf0b4

BIN
depoly_cultural_relics_furniture/seg_detect_Panoramic_Home/__pycache__/add_door_seg_server.cpython-38.pyc


BIN
depoly_cultural_relics_furniture/seg_detect_Panoramic_Home/__pycache__/seg_server.cpython-38.pyc


BIN
depoly_cultural_relics_furniture/seg_detect_Panoramic_Home/door_detect.pt


BIN
depoly_cultural_relics_furniture/seg_detect_Panoramic_Home/res_KJ-vYL6GFH2fYw_30.jpg


+ 21 - 11
depoly_cultural_relics_furniture/seg_detect_Panoramic_Home/res_KJ-vYL6GFH2fYw_30.json

@@ -123,10 +123,22 @@
                 768,
                 896,
                 768,
+                1036,
+                774,
+                1043,
+                780,
+                1043,
+                787,
+                1049,
+                864,
+                1049,
+                870,
                 1056,
-                1011,
+                998,
                 1056,
                 1011,
+                1043,
+                1011,
                 1004,
                 1017,
                 998,
@@ -172,24 +184,22 @@
                 1024,
                 1964,
                 1024,
-                1977,
+                1971,
+                1030,
+                1990,
+                1030,
+                1996,
                 1036,
-                1977,
-                1056,
                 2086,
-                1056,
+                1036,
                 2086,
                 736,
                 2092,
                 729,
                 2092,
                 716,
-                2105,
-                704,
-                2131,
-                704,
-                2131,
-                678,
+                2112,
+                697,
                 2112,
                 678,
                 2105,

BIN
depoly_cultural_relics_furniture/seg_detect_Panoramic_Home/res_nolabel.jpg


+ 111 - 0
depoly_cultural_relics_furniture/seg_detect_Panoramic_Home/res_nolabel.json

@@ -0,0 +1,111 @@
+{
+    "shapes": [
+        {
+            "bbox": [
+                0,
+                609,
+                402,
+                609,
+                402,
+                1657,
+                0,
+                1657
+            ],
+            "seg_points": [
+                0,
+                601,
+                0,
+                1670,
+                147,
+                1670,
+                147,
+                1651,
+                166,
+                1632,
+                179,
+                1632,
+                185,
+                1625,
+                249,
+                1625,
+                256,
+                1619,
+                281,
+                1619,
+                288,
+                1612,
+                300,
+                1612,
+                307,
+                1606,
+                345,
+                1606,
+                352,
+                1600,
+                364,
+                1600,
+                371,
+                1593,
+                377,
+                1593,
+                384,
+                1587,
+                416,
+                1587,
+                416,
+                678,
+                396,
+                678,
+                390,
+                672,
+                377,
+                672,
+                371,
+                665,
+                352,
+                665,
+                345,
+                659,
+                320,
+                659,
+                313,
+                652,
+                307,
+                652,
+                300,
+                646,
+                288,
+                646,
+                281,
+                640,
+                249,
+                640,
+                243,
+                633,
+                211,
+                633,
+                204,
+                627,
+                192,
+                627,
+                185,
+                620,
+                185,
+                601
+            ],
+            "category": "Entrance_Door",
+            "color": [
+                255,
+                128,
+                0
+            ],
+            "label": "67 0.0 0.297363 0.098145 0.297363 0.098145 0.809082 0.0 0.809082",
+            "name": "入户门",
+            "score": 0.9623938798904419
+        }
+    ],
+    "imageHeight": 2048,
+    "imagePath": "nolabel.jpg",
+    "imageWidth": 4096,
+    "version": "4dage_furniture_det_0.0.1"
+}

+ 2 - 2
depoly_cultural_relics_furniture/seg_detect_Panoramic_Home/seg_client_cpu.py

@@ -6,7 +6,7 @@ import argparse
 from PIL import Image, ImageDraw, ImageFont
 
 # --- 配置区 ---
-API_HOST = os.getenv("API_HOST", "192.168.9.113")
+API_HOST = os.getenv("API_HOST", "192.168.9.52")
 API_PORT = os.getenv("API_PORT", "7000")
 BASE_URL = f"http://{API_HOST}:{API_PORT}"
 
@@ -100,4 +100,4 @@ if __name__ == "__main__":
     args = parser.parse_args()
 
     # 运行主逻辑
-    process_image(args.image)
+    process_image(args.image)

+ 0 - 315
depoly_cultural_relics_furniture/seg_detect_Panoramic_Home/seg_results.json

@@ -1,315 +0,0 @@
-{
-    "shapes": [
-        {
-            "bbox": [
-                742,
-                409,
-                2126,
-                409,
-                2126,
-                1052,
-                742,
-                1052
-            ],
-            "seg_points": [
-                1177,
-                396,
-                1177,
-                416,
-                1171,
-                422,
-                1164,
-                422,
-                1158,
-                428,
-                1139,
-                428,
-                1132,
-                435,
-                1107,
-                435,
-                1100,
-                441,
-                1075,
-                441,
-                1068,
-                448,
-                1062,
-                448,
-                1056,
-                454,
-                1049,
-                454,
-                1043,
-                460,
-                1017,
-                460,
-                1011,
-                467,
-                998,
-                467,
-                992,
-                473,
-                979,
-                473,
-                972,
-                480,
-                966,
-                480,
-                960,
-                486,
-                947,
-                486,
-                940,
-                492,
-                928,
-                492,
-                915,
-                505,
-                908,
-                505,
-                902,
-                512,
-                883,
-                512,
-                876,
-                518,
-                870,
-                518,
-                857,
-                531,
-                851,
-                531,
-                844,
-                537,
-                838,
-                537,
-                832,
-                544,
-                825,
-                544,
-                806,
-                563,
-                800,
-                563,
-                793,
-                569,
-                787,
-                569,
-                780,
-                576,
-                774,
-                576,
-                761,
-                588,
-                729,
-                588,
-                729,
-                627,
-                748,
-                627,
-                761,
-                640,
-                761,
-                652,
-                768,
-                659,
-                768,
-                684,
-                761,
-                691,
-                761,
-                889,
-                768,
-                896,
-                768,
-                1056,
-                1011,
-                1056,
-                1011,
-                1004,
-                1017,
-                998,
-                1024,
-                998,
-                1030,
-                992,
-                1126,
-                992,
-                1132,
-                985,
-                1139,
-                985,
-                1145,
-                992,
-                1529,
-                992,
-                1536,
-                998,
-                1619,
-                998,
-                1625,
-                992,
-                1657,
-                992,
-                1664,
-                998,
-                1715,
-                998,
-                1721,
-                1004,
-                1728,
-                1004,
-                1734,
-                1011,
-                1804,
-                1011,
-                1811,
-                1017,
-                1913,
-                1017,
-                1920,
-                1024,
-                1964,
-                1024,
-                1977,
-                1036,
-                1977,
-                1056,
-                2086,
-                1056,
-                2086,
-                736,
-                2092,
-                729,
-                2092,
-                716,
-                2105,
-                704,
-                2131,
-                704,
-                2131,
-                678,
-                2112,
-                678,
-                2105,
-                672,
-                2099,
-                672,
-                2067,
-                640,
-                2060,
-                640,
-                2016,
-                595,
-                2009,
-                595,
-                2003,
-                588,
-                1996,
-                588,
-                1977,
-                569,
-                1971,
-                569,
-                1958,
-                556,
-                1952,
-                556,
-                1939,
-                544,
-                1932,
-                544,
-                1926,
-                537,
-                1913,
-                537,
-                1907,
-                531,
-                1900,
-                531,
-                1888,
-                518,
-                1881,
-                518,
-                1875,
-                512,
-                1862,
-                512,
-                1856,
-                505,
-                1849,
-                505,
-                1843,
-                499,
-                1836,
-                499,
-                1830,
-                492,
-                1824,
-                492,
-                1817,
-                486,
-                1804,
-                486,
-                1798,
-                480,
-                1785,
-                480,
-                1779,
-                473,
-                1772,
-                473,
-                1766,
-                467,
-                1753,
-                467,
-                1747,
-                460,
-                1721,
-                460,
-                1715,
-                454,
-                1702,
-                454,
-                1696,
-                448,
-                1689,
-                448,
-                1683,
-                441,
-                1664,
-                441,
-                1657,
-                435,
-                1625,
-                435,
-                1619,
-                428,
-                1593,
-                428,
-                1587,
-                422,
-                1580,
-                422,
-                1574,
-                416,
-                1574,
-                396
-            ],
-            "category": "Kitchen_Cabinet",
-            "color": [
-                190,
-                255,
-                220
-            ],
-            "label": "25 0.181152 0.199707 0.5190430.1997070.1811520.513672 0.519043 0.513672",
-            "name": "橱柜",
-            "score": 0.9582321643829346
-        }
-    ],
-    "imageHeight": 2048,
-    "imagePath": "KJ-vYL6GFH2fYw_30.jpg",
-    "imageWidth": 4096,
-    "version": "4dage_cul_rel_dis_det_0.0.1"
-}

+ 68 - 4
depoly_cultural_relics_furniture/seg_detect_Panoramic_Home/seg_server.py

@@ -33,7 +33,7 @@ COLOR_LIST = {
     "Bedroom": (140, 10, 140), "Bathroom": (240, 100, 0), "Balcony": (0, 240, 100),
     "Entrance": (100, 0, 240), "Storage_Room": (240, 0, 100), "Community_Name": (0, 100, 240),
     "Community_Gate": (100, 240, 0), "Building_Number": (100, 100, 100), "Floor_Number": (200, 200, 200),
-    "House_Number": (255, 10, 10), "Room_Number": (10, 255, 10)
+    "House_Number": (255, 10, 10), "Room_Number": (10, 255, 10), "Entrance_Door": (255, 128, 0)
 }
 
 english_lables = [
@@ -47,7 +47,7 @@ english_lables = [
     "Washing_Machine", "TV", "Refrigerator", "Wall_Mounted_AC", "Cabinet_AC", "Water_Dispenser",
     "Hair_Dryer", "Microwave", "Grill", "Robot_Vacuum", "Table_Lamp", "Living_Room", "Dining_Room",
     "Bedroom", "Bathroom", "Balcony", "Entrance", "Storage_Room", "Community_Name", "Community_Gate",
-    "Building_Number", "Floor_Number", "House_Number", "Room_Number"
+    "Building_Number", "Floor_Number", "House_Number", "Room_Number", "Entrance_Door"
 ]
 
 label_dic = {
@@ -71,7 +71,7 @@ label_dic = {
     "餐厅": "Dining_Room", "卧室": "Bedroom", "卫生间": "Bathroom", "阳台": "Balcony",
     "玄关": "Entrance", "储物间": "Storage_Room", "小区名": "Community_Name",
     "小区大门": "Community_Gate", "楼号": "Building_Number", "楼层": "Floor_Number",
-    "门牌号": "House_Number", "房号": "Room_Number"
+    "门牌号": "House_Number", "房号": "Room_Number", "门": "Entrance_Door"
 }
 
 
@@ -144,12 +144,69 @@ def parse_seg_results(results, filename):
 models = {}
 
 
+def detect_entrance_door(image, model):
+    """使用 door_detect.pt 检测入户门,返回得分最高的检测结果(置信度>0.35)"""
+    results = model(image, imgsz=640, device='cpu')
+
+    for result in results:
+        box = result.boxes.cpu().numpy()
+        if box.shape[0] == 0:
+            return None
+
+        # 找到置信度最高的检测框
+        max_idx = None
+        max_conf = 0.35  # 阈值
+        for i in range(len(box.conf)):
+            if box.conf[i] > max_conf:
+                max_conf = box.conf[i]
+                max_idx = i
+
+        if max_idx is None:
+            return None
+
+        # 获取最高分检测框的信息
+        xywh = box.xywh[0]
+        x_c, y_c, w, h = int(xywh[0]), int(xywh[1]), int(xywh[2]), int(xywh[3])
+        x = int(x_c - w / 2)
+        y = int(y_c - h / 2)
+        x1, y1, x2, y2 = x, y, x + w, y + h
+
+        imageHeight, imageWidth = result.orig_shape
+
+
+    yolov_x = [round(v / imageWidth, 6) for v in [x1, x2, x2, x1]]
+    yolov_y = [round(v / imageHeight, 6) for v in [y1, y1, y2, y2]]
+
+    # 获取分割点
+    seg_points = []
+    if result.masks is not None:
+        segments_xy = result.masks.xy[0]
+        length = segments_xy.shape[0]
+        for i in range(length):
+            px, py = int(segments_xy[i, 0]), int(segments_xy[i, 1])
+            seg_points.append(px)
+            seg_points.append(py)
+
+    return {
+        "bbox": [x1, y1, x2, y1, x2, y2, x1, y2],
+        "seg_points": seg_points if seg_points else None,
+        "category": "Entrance_Door",
+        "color": COLOR_LIST["Entrance_Door"],
+        "label": f"67 {yolov_x[0]} {yolov_y[0]} {yolov_x[1]} {yolov_y[1]} {yolov_x[2]} {yolov_y[2]} {yolov_x[3]} {yolov_y[3]}",
+        "name": "入户门",
+        "score": float(box.conf[max_idx])
+    }
+
+    return None
+
+
 @asynccontextmanager
 async def lifespan(app: FastAPI):
     # 在应用启动时执行
     print("应用启动...开始加载模型...")
 
     models["seg"] = YOLO('seg_detect_Panoramic_Home.pt')
+    models["door"] = YOLO('door_detect.pt')
 
     print("模型加载完成!")
     yield
@@ -183,8 +240,15 @@ async def predict_seg(data: dict):
 
         # 使用加载好的模型进行推理
         # results = models["seg"](image, imgsz=2560)
-        results = models["seg"](image, imgsz=640,device='cpu')
+        results = models["seg"](image, imgsz=640, device='cpu')
         segs, imginf = parse_seg_results(results, data.get("filename"))
+        # 检测入户门
+        entrance_door = detect_entrance_door(image, models["door"])
+        if entrance_door:
+            # 如果家具没有检测到(无效条目),删除无效条目再添加门
+            if segs and segs[0]["bbox"] is None and segs[0]["seg_points"] is None:
+                segs.pop(0)
+            segs.append(entrance_door)
 
         content_data = JSONResponse(content={"shapes": segs}).body.decode("utf-8")  # 获取原始JSON字符串
         data_dict = json.loads(content_data)  # 解析为字典

+ 205 - 0
depoly_cultural_relics_furniture/seg_detect_Panoramic_Home/seg_server_base.py

@@ -0,0 +1,205 @@
+import io
+from PIL import Image
+from fastapi import FastAPI, File, UploadFile, HTTPException
+from fastapi.responses import JSONResponse
+from ultralytics import YOLO
+from contextlib import asynccontextmanager
+import base64
+import json
+import os
+import uvicorn
+import numpy as np
+
+COLOR_LIST = {
+    "Single_Door": (255, 0, 0), "Double_Door": (0, 255, 0), "Sliding_Door": (0, 0, 255),
+    "Doorway_Opening": (255, 255, 0), "Mother_Son_Door": (0, 255, 255), "Revolving_Door": (255, 0, 255),
+    "Rolling_Gate": (192, 192, 192), "Rolling_Shutter_Door": (128, 0, 0), "Door_Window_Combo": (128, 128, 0),
+    "Casement_Window": (0, 128, 0), "Sliding_Window": (128, 0, 128), "French_Window": (0, 128, 128),
+    "Bay_Window": (0, 0, 128), "Single_Bay_Window": (255, 165, 0), "Three_Seat_Sofa": (255, 192, 203),
+    "Single_Seat_Sofa": (100, 149, 237), "Corner_Sofa": (255, 215, 0), "Coffee_Table": (218, 165, 32),
+    "Side_Table": (255, 170, 170), "TV_Cabinet": (170, 255, 170), "Wardrobe": (170, 170, 255),
+    "Dressing_Table": (255, 255, 170), "Nightstand": (170, 255, 255), "Shoe_Cabinet": (255, 170, 255),
+    "Sideboard": (255, 220, 190), "Kitchen_Cabinet": (190, 255, 220), "Bathroom_Cabinet": (220, 190, 255),
+    "Bookshelf": (255, 190, 220), "Dining_Table": (190, 220, 255), "Desk": (220, 255, 190),
+    "Chair": (130, 220, 130), "Stool": (220, 130, 130), "Bench": (130, 130, 220),
+    "Double_Bed": (220, 220, 130), "Single_Bed": (130, 220, 220), "Gas_Stove": (220, 130, 220),
+    "Sink": (150, 50, 50), "Bathtub": (50, 150, 50), "Toilet": (50, 50, 150),
+    "Squat_Toilet": (150, 150, 50), "Vanity_Unit": (50, 150, 150), "Shower_Head": (150, 50, 150),
+    "Mop_Sink": (100, 0, 0), "Rug": (0, 100, 0), "Washing_Machine": (0, 0, 100),
+    "TV": (100, 100, 0), "Refrigerator": (0, 100, 100), "Wall_Mounted_AC": (100, 0, 100),
+    "Cabinet_AC": (75, 75, 75), "Water_Dispenser": (175, 175, 175), "Hair_Dryer": (25, 25, 25),
+    "Microwave": (225, 225, 225), "Grill": (10, 140, 10), "Robot_Vacuum": (140, 10, 10),
+    "Table_Lamp": (10, 10, 140), "Living_Room": (140, 140, 10), "Dining_Room": (10, 140, 140),
+    "Bedroom": (140, 10, 140), "Bathroom": (240, 100, 0), "Balcony": (0, 240, 100),
+    "Entrance": (100, 0, 240), "Storage_Room": (240, 0, 100), "Community_Name": (0, 100, 240),
+    "Community_Gate": (100, 240, 0), "Building_Number": (100, 100, 100), "Floor_Number": (200, 200, 200),
+    "House_Number": (255, 10, 10), "Room_Number": (10, 255, 10)
+}
+
+english_lables = [
+    "Single_Door", "Double_Door", "Sliding_Door", "Doorway_Opening", "Mother_Son_Door", "Rolling_Gate",
+    "Rolling_Shutter_Door", "Revolving_Door", "Door_Window_Combo", "Casement_Window", "Sliding_Window",
+    "French_Window", "Bay_Window", "Single_Bay_Window", "Three_Seat_Sofa", "Single_Seat_Sofa",
+    "Corner_Sofa", "Coffee_Table", "Side_Table", "TV_Cabinet", "Wardrobe", "Dressing_Table",
+    "Nightstand", "Shoe_Cabinet", "Sideboard", "Kitchen_Cabinet", "Bathroom_Cabinet", "Bookshelf",
+    "Dining_Table", "Desk", "Chair", "Stool", "Bench", "Double_Bed", "Single_Bed", "Gas_Stove",
+    "Sink", "Bathtub", "Toilet", "Squat_Toilet", "Vanity_Unit", "Shower_Head", "Mop_Sink", "Rug",
+    "Washing_Machine", "TV", "Refrigerator", "Wall_Mounted_AC", "Cabinet_AC", "Water_Dispenser",
+    "Hair_Dryer", "Microwave", "Grill", "Robot_Vacuum", "Table_Lamp", "Living_Room", "Dining_Room",
+    "Bedroom", "Bathroom", "Balcony", "Entrance", "Storage_Room", "Community_Name", "Community_Gate",
+    "Building_Number", "Floor_Number", "House_Number", "Room_Number"
+]
+
+label_dic = {
+    "单开门": "Single_Door", "双开门": "Double_Door", "移门": "Sliding_Door", "垭口": "Doorway_Opening",
+    "子母门": "Mother_Son_Door", "拉闸门": "Rolling_Gate", "卷闸门": "Rolling_Shutter_Door",
+    "旋转门": "Revolving_Door", "门联窗": "Door_Window_Combo", "平开窗": "Casement_Window",
+    "推拉窗": "Sliding_Window", "落地窗": "French_Window", "飘窗": "Bay_Window",
+    "单面飘窗": "Single_Bay_Window", "三人沙发": "Three_Seat_Sofa", "单人沙发": "Single_Seat_Sofa",
+    "转角沙发": "Corner_Sofa", "茶几": "Coffee_Table", "边几": "Side_Table",
+    "电视柜": "TV_Cabinet", "衣柜": "Wardrobe", "梳妆台": "Dressing_Table",
+    "床头柜": "Nightstand", "鞋柜": "Shoe_Cabinet", "边柜": "Sideboard",
+    "橱柜": "Kitchen_Cabinet", "浴室柜": "Bathroom_Cabinet", "书柜": "Bookshelf",
+    "餐桌": "Dining_Table", "书桌": "Desk", "椅子": "Chair", "凳子": "Stool",
+    "条凳": "Bench", "双人床": "Double_Bed", "单人床": "Single_Bed", "燃气灶": "Gas_Stove",
+    "水槽": "Sink", "浴缸": "Bathtub", "马桶": "Toilet", "蹲便器": "Squat_Toilet",
+    "洗漱台": "Vanity_Unit", "花洒": "Shower_Head", "拖把池": "Mop_Sink", "地毯": "Rug",
+    "洗衣机": "Washing_Machine", "电视": "TV", "冰箱": "Refrigerator",
+    "挂式空调": "Wall_Mounted_AC", "柜式空调": "Cabinet_AC", "饮水机": "Water_Dispenser",
+    "吹风机": "Hair_Dryer", "微波炉": "Microwave", "烧烤架": "Grill",
+    "扫地机器人": "Robot_Vacuum", "台灯": "Table_Lamp", "客厅": "Living_Room",
+    "餐厅": "Dining_Room", "卧室": "Bedroom", "卫生间": "Bathroom", "阳台": "Balcony",
+    "玄关": "Entrance", "储物间": "Storage_Room", "小区名": "Community_Name",
+    "小区大门": "Community_Gate", "楼号": "Building_Number", "楼层": "Floor_Number",
+    "门牌号": "House_Number", "房号": "Room_Number"
+}
+
+
+def parse_seg_results(results, filename):
+    """解析旋转框检测结果"""
+    detect_results = []
+    imageHeight, imageWidth = results[0].orig_shape
+    imagePath = os.path.basename(filename)
+    img_inf = (imagePath, imageWidth, imageHeight)
+
+    segs = []
+    for result in results:
+        xywh = result.boxes.xywh.cpu().numpy()
+        box = result.boxes.cpu().numpy()
+        if box.shape[0] == 0:
+            segs.append({
+                "bbox": None,
+                "seg_points": None,
+                "category": None,
+                "color": None,
+                "label": None,
+                "name": None,
+                "score": None
+            })
+        else:
+            x_c, y_c, w, h = int(xywh[0, 0]), int(xywh[0, 1]), int(xywh[0, 2]), int(xywh[0, 3])
+            x = int(x_c - w / 2)
+            y = int(y_c - h / 2)
+            x1, y1, x2, y2 = x, y, x + w, y + h  # 左上角顶点和右下角顶点
+
+            ##############
+            points_list = []
+            tlp = (x1, y1)
+            trp = (x2, y1)
+            blp = (x1, y2)
+            brp = (x2, y2)
+            points_list.append(tlp)
+            points_list.append(trp)
+            points_list.append(blp)
+            points_list.append(brp)
+            all_x = [point[0] for point in points_list]
+            all_y = [point[1] for point in points_list]
+            ##############
+
+            segments_xy = result.masks.xy[0]  # 归一化坐标,形状通常是 (N, 2) 的一维数组
+            imageHeight, imageWidth = result.orig_shape
+            seg_points = []
+            length = segments_xy.shape[0]
+
+            for i in range(length):
+                x, y = (int(segments_xy[i, :][0]), int(segments_xy[i, :][1]))
+                seg_points.append(x)
+                seg_points.append(y)
+
+            yolov_x = [round(x / imageWidth, 6) for x in all_x]
+            yolov_y = [round(y / imageHeight, 6) for y in all_y]
+
+            segs.append({
+                "bbox": [x1, y1, x2, y1, x2, y2, x1, y2],
+                "seg_points": seg_points,
+                "category": label_dic[result.names[int(box.cls[0])]],
+                "color": COLOR_LIST[label_dic[result.names[int(box.cls[0])]]],
+                "label": f"{int(box.cls[0])} {yolov_x[0]} {yolov_y[0]} {yolov_x[1]}{yolov_y[1]}{yolov_x[2]}{yolov_y[2]} {yolov_x[3]} {yolov_y[3]}",
+                "name": result.names[int(box.cls[0])],
+                "score": float(box.conf[0])
+            })
+    return segs, img_inf
+
+
+models = {}
+
+
+@asynccontextmanager
+async def lifespan(app: FastAPI):
+    # 在应用启动时执行
+    print("应用启动...开始加载模型...")
+
+    models["seg"] = YOLO('seg_detect_Panoramic_Home.pt')
+
+    print("模型加载完成!")
+    yield
+    # 在应用关闭时执行 (如果需要)
+    print("应用关闭...")
+    models.clear()
+
+
+app = FastAPI(lifespan=lifespan)
+
+
+@app.get("/")
+def read_root():
+    return {"message": "欢迎使用 YOLO 系列模型部署 API"}
+
+
+@app.post("/seg")  # 假设您将此端点修改为接收 JSON 数据
+async def predict_seg(data: dict):
+    """旋转框检测任务,接收 Base64 编码的图片"""
+    try:
+        # 从 JSON 中获取 Base64 字符串
+        base64_string = data.get("image_data")
+        if not base64_string:
+            raise HTTPException(status_code=400, detail="未找到 'image_data' 字段")
+
+        # 解码 Base64 字符串
+        image_bytes = base64.b64decode(base64_string)
+
+        # 将字节流转换为 PIL.Image 对象
+        image = Image.open(io.BytesIO(image_bytes)).convert("RGB")
+
+        # 使用加载好的模型进行推理
+        # results = models["seg"](image, imgsz=2560)
+        results = models["seg"](image, imgsz=640,device='cpu')
+        segs, imginf = parse_seg_results(results, data.get("filename"))
+
+        content_data = JSONResponse(content={"shapes": segs}).body.decode("utf-8")  # 获取原始JSON字符串
+        data_dict = json.loads(content_data)  # 解析为字典
+        # ✅ 添加新数据到shapes列表
+        data_dict.update({
+            "imageHeight": imginf[2],
+            "imagePath": imginf[0],
+            "imageWidth": imginf[1],
+            "version": "4dage_furniture_det_0.0.1"
+        })
+
+        return JSONResponse(content=data_dict)
+    except Exception as e:
+        raise HTTPException(status_code=500, detail=f"处理图片时出错: {str(e)}")
+
+
+if __name__ == "__main__":
+    uvicorn.run("seg_server:app", host="0.0.0.0", port=7000, reload=True)

+ 167 - 28
door_detect/export_entrance_position.py

@@ -1055,6 +1055,118 @@ class EntranceDoorDetector:
 
         return self.entrance_door is not None or self.estimated_entrance_position is not None
 
+    def _get_endpoint_positions(self) -> Tuple[Optional[np.ndarray], Optional[np.ndarray]]:
+        """获取 vision.txt 中 id 最小和最大的两个点位的世界坐标
+
+        Returns:
+            (first_pos, last_pos): id 最小的点位位置和 id 最大的点位位置 (世界坐标 XY)
+        """
+        if not self.poses:
+            return None, None
+
+        # 按 id 排序
+        sorted_poses = sorted(self.poses.values(), key=lambda p: p.pose_id)
+        first_pose = sorted_poses[0]
+        last_pose = sorted_poses[-1]
+
+        first_pos = np.array([
+            first_pose.translation['x'],
+            first_pose.translation['y'],
+            first_pose.translation['z']
+        ])
+        last_pos = np.array([
+            last_pose.translation['x'],
+            last_pose.translation['y'],
+            last_pose.translation['z']
+        ])
+
+        return first_pos, last_pos
+
+    def _build_entrance_candidates(
+        self,
+        entrance_pos: np.ndarray
+    ) -> List[Dict[str, float]]:
+        """构建入户门候选点列表
+
+        Args:
+            entrance_pos: 计算得到的入户门位置 (世界坐标)
+
+        Returns:
+            候选点列表,每个候选点为 {"x": x, "y": y, "z": z} 格式
+        """
+        first_pos, last_pos = self._get_endpoint_positions()
+
+        if first_pos is None or last_pos is None:
+            # 没有点位信息,只返回计算的入户门位置
+            return [{
+                "x": float(entrance_pos[0]),
+                "y": float(entrance_pos[1]),
+                "z": float(entrance_pos[2])
+            }]
+
+        # 检查估计位置是否等于端点位置 (2D 距离判断)
+        def is_same_position(p1: np.ndarray, p2: np.ndarray, thresh: float = 0.01) -> bool:
+            dist_2d = np.linalg.norm(p1[:2] - p2[:2])
+            return dist_2d < thresh
+
+        if is_same_position(entrance_pos, first_pos):
+            # 估计位置等于第一个点位,只返回 2 个候选点
+            return [
+                {
+                    "x": float(entrance_pos[0]),
+                    "y": float(entrance_pos[1]),
+                    "z": float(entrance_pos[2])
+                },
+                {
+                    "x": float(last_pos[0]),
+                    "y": float(last_pos[1]),
+                    "z": float(last_pos[2])
+                }
+            ]
+
+        if is_same_position(entrance_pos, last_pos):
+            # 估计位置等于最后一个点位,只返回 2 个候选点
+            return [
+                {
+                    "x": float(entrance_pos[0]),
+                    "y": float(entrance_pos[1]),
+                    "z": float(entrance_pos[2])
+                },
+                {
+                    "x": float(first_pos[0]),
+                    "y": float(first_pos[1]),
+                    "z": float(first_pos[2])
+                }
+            ]
+
+        # 计算 2D 距离
+        dist_to_first = np.linalg.norm(entrance_pos[:2] - first_pos[:2])
+        dist_to_last = np.linalg.norm(entrance_pos[:2] - last_pos[:2])
+
+        # 距离远者为第 2 候选点,近者为第 3 候选点
+        if dist_to_first > dist_to_last:
+            second_pos, third_pos = first_pos, last_pos
+        else:
+            second_pos, third_pos = last_pos, first_pos
+
+        return [
+            {
+                "x": float(entrance_pos[0]),
+                "y": float(entrance_pos[1]),
+                "z": float(entrance_pos[2])
+            },
+            {
+                "x": float(second_pos[0]),
+                "y": float(second_pos[1]),
+                "z": float(second_pos[2])
+            },
+            {
+                "x": float(third_pos[0]),
+                "y": float(third_pos[1]),
+                "z": float(third_pos[2])
+            }
+        ]
+
     def export_json(self, output_path: Optional[str] = None) -> str:
         """导出结果到 JSON 文件"""
         if output_path is None:
@@ -1081,11 +1193,10 @@ class EntranceDoorDetector:
             ]
             avg_confidence = np.mean(detection_scores) if detection_scores else 0.0
 
-            result["entrance_position"] = {
-                "x": float(door.center[0]),
-                "y": float(door.center[1]),
-                "z": float(door.center[2])
-            }
+            entrance_pos = door.center
+            entrance_candidates = self._build_entrance_candidates(entrance_pos)
+
+            result["entrance_candidates"] = entrance_candidates
             result["source"] = "door_detection"
             result["is_estimated"] = False
 
@@ -1129,12 +1240,9 @@ class EntranceDoorDetector:
         elif self.estimated_entrance_position is not None:
             # 从点位估计
             pos = self.estimated_entrance_position
+            entrance_candidates = self._build_entrance_candidates(pos)
 
-            result["entrance_position"] = {
-                "x": float(pos[0]),
-                "y": float(pos[1]),
-                "z": float(pos[2])
-            }
+            result["entrance_candidates"] = entrance_candidates
             result["source"] = "pose_estimation"
             result["is_estimated"] = True
 
@@ -1154,7 +1262,7 @@ class EntranceDoorDetector:
 
         else:
             # 无法确定
-            result["entrance_position"] = None
+            result["entrance_candidates"] = None
             result["source"] = "unknown"
             result["is_estimated"] = False
             result["error"] = "未检测到门且无法从点位估计"
@@ -1177,16 +1285,23 @@ class EntranceDoorDetector:
         Args:
             pc: 场景点云(带颜色)
             output_path: 输出路径,默认为 scene/output/vis.ply
+
+        候选点颜色:
+            - 第 1 候选点:红色 (1.0, 0.0, 0.0)
+            - 第 2 候选点:绿色 (0.0, 1.0, 0.0)
+            - 第 3 候选点:蓝色 (0.0, 0.0, 1.0)
         """
-        # 获取入户门位置
-        position = None
+        # 获取候选点列表
+        entrance_candidates = []
         if self.entrance_door is not None:
-            position = self.entrance_door.center
+            entrance_pos = self.entrance_door.center
+            entrance_candidates = self._build_entrance_candidates(entrance_pos)
         elif self.estimated_entrance_position is not None:
-            position = self.estimated_entrance_position
+            entrance_pos = self.estimated_entrance_position
+            entrance_candidates = self._build_entrance_candidates(entrance_pos)
 
-        if position is None:
-            print("⚠️ 没有入户门位置,跳过可视化")
+        if not entrance_candidates:
+            print("⚠️ 没有入户门候选点,跳过可视化")
             return
 
         # 下采样场景点云(减少文件大小)
@@ -1199,15 +1314,35 @@ class EntranceDoorDetector:
         else:
             pc_colors = np.asarray(pc_vis.colors)
 
-        # 创建红色球体点云
-        sphere = o3d.geometry.TriangleMesh.create_sphere(radius=0.2, resolution=30)
-        sphere.translate(position)
-        sphere_pc = sphere.sample_points_uniformly(number_of_points=2000)
-        sphere_colors = np.tile([1.0, 0.0, 0.0], (len(sphere_pc.points), 1))  # 红色
+        # 候选点颜色:红、绿、蓝
+        candidate_colors = [
+            [1.0, 0.0, 0.0],  # 红色 - 第 1 候选点
+            [0.0, 1.0, 0.0],  # 绿色 - 第 2 候选点
+            [0.0, 0.0, 1.0],  # 蓝色 - 第 3 候选点
+        ]
+
+        # 创建球体点云列表
+        sphere_points_list = []
+        sphere_colors_list = []
+
+        for i, candidate in enumerate(entrance_candidates):
+            if i >= len(candidate_colors):
+                break
+
+            pos = np.array([candidate["x"], candidate["y"], candidate["z"]])
+            color = candidate_colors[i]
+
+            sphere = o3d.geometry.TriangleMesh.create_sphere(radius=0.2, resolution=30)
+            sphere.translate(pos)
+            sphere_pc = sphere.sample_points_uniformly(number_of_points=2000)
+            sphere_colors = np.tile(color, (len(sphere_pc.points), 1))
+
+            sphere_points_list.append(np.asarray(sphere_pc.points))
+            sphere_colors_list.append(sphere_colors)
 
-        # 合并点云
-        combined_points = np.vstack([np.asarray(pc_vis.points), np.asarray(sphere_pc.points)])
-        combined_colors = np.vstack([pc_colors, sphere_colors])
+        # 合并所有点云
+        combined_points = np.vstack([np.asarray(pc_vis.points)] + sphere_points_list)
+        combined_colors = np.vstack([pc_colors] + sphere_colors_list)
 
         # 创建输出点云
         output_pc = o3d.geometry.PointCloud()
@@ -1225,9 +1360,13 @@ class EntranceDoorDetector:
         o3d.io.write_point_cloud(str(output_path), output_pc, write_ascii=True, print_progress=False)
 
         print(f"可视化已导出:{output_path}")
-        print(f"  - 入户门位置:{position}")
-        print(f"  - 红色球体半径:0.2m")
-        print(f"  - 总点数:{len(output_pc.points)} (场景:{len(pc_vis.points)}, 球体:{len(sphere_pc.points)})")
+        print(f"  - 候选点数量:{len(entrance_candidates)}")
+        for i, candidate in enumerate(entrance_candidates):
+            color_names = ["红色", "绿色", "蓝色"]
+            color_name = color_names[i] if i < len(color_names) else f"颜色{i}"
+            print(f"  - 第{i+1}候选点 ({color_name}): [{candidate['x']:.3f}, {candidate['y']:.3f}, {candidate['z']:.3f}]")
+        print(f"  - 球体半径:0.2m")
+        print(f"  - 总点数:{len(output_pc.points)} (场景:{len(pc_vis.points)}, 球体:{len(entrance_candidates) * 2000})")
 
 
 # ============================================================================

+ 17 - 5
door_detect/scene0001/output/entrance_position.json

@@ -1,10 +1,22 @@
 {
   "scene_name": "scene0001",
-  "entrance_position": {
-    "x": 5.243888370027931,
-    "y": -1.8375943485995254,
-    "z": -0.3358584814547927
-  },
+  "entrance_candidates": [
+    {
+      "x": 5.243888370027931,
+      "y": -1.8375943485995254,
+      "z": -0.3358584814547927
+    },
+    {
+      "x": -0.6589143223498,
+      "y": 1.417002444251,
+      "z": 0.0018424056214
+    },
+    {
+      "x": 7.043951739195,
+      "y": -1.398693435379,
+      "z": -0.0718082414545
+    }
+  ],
   "source": "door_detection",
   "is_estimated": false,
   "door_info": {

ファイルの差分が大きいため隠しています
+ 6001 - 2001
door_detect/scene0001/output/vis.ply