Skip to content

Commit 55475ee

Browse files
committed
find valid parking lot
1 parent a4e0c4d commit 55475ee

28 files changed

Lines changed: 751 additions & 66 deletions

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,4 +176,5 @@ cuda/
176176

177177
**/rosbag/*
178178
**/logs/*
179+
**/*.bag
179180

Fusion/ClickPoints.py

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import os
2+
import cv2
3+
import time
4+
import numpy as np
5+
6+
7+
def clickPoints(imgPath=None, numPoints=4):
8+
clicked_pts = []
9+
10+
def mouse_callback(event, x, y, flags, param):
11+
if event == cv2.EVENT_LBUTTONDOWN and len(param) < numPoints:
12+
param.append((x, y))
13+
print(f"点击第 {len(param)} 个点: ({x}, {y})")
14+
15+
# 初始化图像
16+
if imgPath:
17+
img = cv2.imread(imgPath, flags=cv2.IMREAD_COLOR)
18+
else:
19+
img = np.ones((600, 800, 3), dtype=np.uint8) * 255 # 白色背景
20+
21+
cv2.namedWindow("Click Points")
22+
cv2.setMouseCallback("Click Points", mouse_callback, clicked_pts)
23+
24+
while True:
25+
img_copy = img.copy()
26+
27+
# 画点击点
28+
for pt in clicked_pts:
29+
cv2.circle(img_copy, pt, 5, (0, 0, 255), -1)
30+
31+
if len(clicked_pts) == numPoints:
32+
cv2.polylines(img_copy, [np.array(clicked_pts)], isClosed=True, color=(0, 255, 0), thickness=2)
33+
34+
cv2.imshow("Click Points", img_copy)
35+
36+
key = cv2.waitKey(10)
37+
if key == 27 or key == ord("q"): # ESC
38+
break
39+
elif key == ord('r'):
40+
clicked_pts.clear()
41+
42+
cv2.destroyAllWindows()
43+
44+
# 输出
45+
if len(clicked_pts) == numPoints:
46+
print("\n最终点击的坐标:")
47+
for i, pt in enumerate(clicked_pts):
48+
print(f"点 {i+1}: {pt}")
49+
50+
return np.array(clicked_pts, dtype=np.float32)
51+
52+
53+
if __name__ == "__main__":
54+
imgPath = os.path.join(
55+
os.path.dirname(__file__),
56+
r"./Pics/FrontCam.png"
57+
)
58+
59+
clickPoints(imgPath)

Fusion/FindRectangle.py

Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
1+
import cv2
2+
import os
3+
import numpy as np
4+
5+
from ClickPoints import clickPoints
6+
7+
8+
9+
GEM_E4_LENGTH = 3.6 # m
10+
GEM_E4_WIDTH = 1.5 # m
11+
12+
13+
14+
15+
16+
17+
def judgeRectInPolygon(rectPts:np.ndarray, polygonPts:np.ndarray):
18+
polygon = polygonPts.reshape((-1, 1, 2)).astype(np.float32)
19+
for pt in rectPts:
20+
inside = cv2.pointPolygonTest(polygon, tuple(pt), measureDist=False)
21+
if inside < 0:
22+
return False
23+
return True
24+
25+
26+
def findAllCandidateParkingLot(cornerPts, angleStepDegree=5, positionStrideMeter=0.5):
27+
28+
cornerPts = np.array(cornerPts, dtype=np.float32)
29+
30+
# 先得到 bounding box 范围(缩小一些,避免跑出区域)
31+
min_x = np.min(cornerPts[:, 0]) + 0.5
32+
max_x = np.max(cornerPts[:, 0]) - 0.5
33+
min_y = np.min(cornerPts[:, 1]) + 0.5
34+
max_y = np.max(cornerPts[:, 1]) - 0.5
35+
36+
candidates = []
37+
38+
for angleDegree in np.arange(-90, 90, angleStepDegree):
39+
rect = ((0, 0), (GEM_E4_LENGTH, GEM_E4_WIDTH), float(angleDegree))
40+
carBox = cv2.boxPoints(rect) # 车体角点,以原点为中心
41+
42+
for cx in np.arange(min_x, max_x, positionStrideMeter):
43+
for cy in np.arange(min_y, max_y, positionStrideMeter):
44+
car_box_shifted = carBox + np.array([cx, cy])
45+
46+
if judgeRectInPolygon(car_box_shifted, cornerPts):
47+
candidates.append((cx, cy, angleDegree))
48+
49+
return candidates
50+
51+
52+
def drawCarPose(img, center, angleDegree, color=(0, 0, 255), scale=100):
53+
rect = (center, (GEM_E4_LENGTH, GEM_E4_WIDTH), float(angleDegree))
54+
box = cv2.boxPoints(rect) # 四个角点
55+
box_scaled = np.int32(box * scale)
56+
57+
cv2.polylines(img, [box_scaled], isClosed=True, color=color, thickness=2)
58+
59+
60+
def visualizeCandidateCarPoses(cornerPts, candidates, scale=100):
61+
img = np.zeros((1000, 1000, 3), dtype=np.uint8)
62+
63+
# 画停车位边框
64+
parking_polygon = np.int32(cornerPts * scale)
65+
cv2.polylines(img, [parking_polygon], isClosed=True, color=(0, 255, 0), thickness=2)
66+
67+
# 画所有合法停车姿态
68+
for (x, y, angle) in candidates:
69+
drawCarPose(img, (x, y), angle, color=(100, 100, 255), scale=scale)
70+
71+
cv2.imshow("Parking Candidates", img)
72+
cv2.waitKey(0)
73+
cv2.destroyAllWindows()
74+
75+
76+
if __name__ == "__main__":
77+
# cornerPts = np.array([
78+
# [8,9],
79+
# [4,5],
80+
# [1,15],
81+
# [5,9],
82+
# ]).astype(np.float32) # any order
83+
84+
cornerPts = np.random.randint(
85+
low=1,
86+
high=10,
87+
size=(4,2)
88+
).astype(np.float32)
89+
90+
cornerPts = clickPoints(
91+
imgPath = os.path.join(
92+
os.path.dirname(__file__),
93+
r"./Pics/FrontCam.png"
94+
)
95+
)/100
96+
97+
print(f"\
98+
cornerPts = np.array([\n\
99+
[{cornerPts[0,0]},{cornerPts[0,1]}],\n\
100+
[{cornerPts[1,0]},{cornerPts[1,1]}],\n\
101+
[{cornerPts[2,0]},{cornerPts[2,1]}],\n\
102+
[{cornerPts[3,0]},{cornerPts[3,1]}],\n\
103+
]).astype(np.float32) # any order \
104+
")
105+
106+
candidates = findAllCandidateParkingLot(cornerPts)
107+
108+
print(f"find {len(candidates)} candidates")
109+
for idx, pose in enumerate(candidates[::10]):
110+
thetaDegree = -(90 - (-pose[2])) if abs(- (90 - (-pose[2]))) < 90 else 180 + (-(90 - (-pose[2])))
111+
print(f"Candidate {idx}, Pose: centerXY:({pose[0]}, {pose[1]}), thetaDegree:{thetaDegree}")
112+
# OpenCV (rotation is from the lowest point, range [-90, 0])
113+
# counter-clock-wise is negative
114+
# clock-wise is positive
115+
116+
117+
visualizeCandidateCarPoses(cornerPts, candidates[::10])
118+
119+
120+
121+
122+
123+
124+
125+
126+
127+
128+
129+
130+
131+
132+
133+
134+
135+
136+
# def checkFitWithMinAreaRect(pts:np.ndarray, rectLongMin=GEM_E4_LENGTH, rectShortMin=GEM_E4_WIDTH):
137+
138+
# def getRotatedRectBox(center, size, angleDegree):
139+
# """
140+
# 给定中心坐标、尺寸和角度,返回矩形4个顶点
141+
# center: (x, y)
142+
# size: (width, height)
143+
# angleDegree: 逆时针旋转角度(OpenCV风格)
144+
# """
145+
# rect = (center, size, angleDegree)
146+
# box = cv2.boxPoints(rect) # 得到4个点
147+
# return box # 转换为整数像素点或保留float点
148+
149+
# # 外接矩形
150+
# outRect = cv2.minAreaRect(pts)
151+
# (centerX, centerY), (width, length), rotation = outRect
152+
# print(f"rotation: {rotation}")
153+
# # OpenCV 返回的 rotation 单位是 角度,范围为 [-90°, 0°]
154+
# # -90° 矩形长边在 Y 轴方向
155+
# # 0° 长边在 X 轴方向
156+
157+
# SwitchOrder = False
158+
# if width > length:
159+
# outBoxLong, outBoxShort = width, length
160+
# else:
161+
# outBoxLong, outBoxShort = length, width
162+
# SwitchOrder = True
163+
# print("SwitchOrder")
164+
165+
# canFit = True
166+
# if SwitchOrder is True:
167+
# rotation += 90
168+
169+
170+
# # judge fitting
171+
# if outBoxLong < rectLongMin:
172+
# print(f"CAN NOT FIT : outBoxLong{outBoxLong} < rectLongMin{rectLongMin}")
173+
# canFit = False
174+
# if outBoxShort < rectShortMin:
175+
# print(f"CAN NOT FIT : outBoxShort{outBoxShort} < rectShortMin{rectShortMin}")
176+
# canFit = False
177+
178+
# centerXY = (centerX, centerY)
179+
180+
181+
# outerBoxPts = getRotatedRectBox(centerXY, (outBoxLong, outBoxShort), rotation)
182+
# innerBoxPts = getRotatedRectBox(centerXY, (rectLongMin, rectShortMin), rotation)
183+
184+
# return canFit, centerXY, rotation, innerBoxPts, outerBoxPts
185+
186+
187+
# if __name__ == "__main__":
188+
# cornerPts = np.array([
189+
# [8,10],
190+
# [3,10],
191+
# [5,5],
192+
# [1,5],
193+
194+
# ]).astype(np.float32) # any order
195+
196+
# cornerPts = np.random.randint(
197+
# low=1,
198+
# high=10,
199+
# size=(4,2)
200+
# ).astype(np.float32)
201+
202+
# print(cornerPts)
203+
204+
205+
# canFit, center, rotation, boxInner, boxOuter = checkFitWithMinAreaRect(cornerPts)
206+
207+
# print(canFit, rotation)
208+
209+
# img = np.zeros((600, 600, 3), dtype=np.uint8)
210+
# ptsInt = np.int32(cornerPts * 40) # 放大方便看
211+
# boxIntInner = np.int32(boxInner * 40)
212+
# boxIntOuter = np.int32(boxOuter * 40)
213+
214+
# cv2.polylines(img, [ptsInt], isClosed=True, color=(0,255,0), thickness=2)
215+
# cv2.polylines(img, [boxIntInner], isClosed=True, color=(255,0,0), thickness=2)
216+
# cv2.polylines(img, [boxIntOuter], isClosed=True, color=(0,0,255), thickness=2)
217+
218+
# cv2.imshow("fit check", img)
219+
# cv2.waitKey(0)
220+
# cv2.destroyAllWindows()
221+
222+

Fusion/Pics/FrontCam.png

901 KB
Loading
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import rospy
2+
import cv2
3+
import numpy as np
4+
from cv_bridge import CvBridge
5+
from sensor_msgs.msg import Image, PointCloud2
6+
from std_msgs.msg import Header
7+
import sensor_msgs.point_cloud2 as pc2
8+
9+
10+
from stitch import cvtImg2PointCloudMsg, maskCamKeyArea, \
11+
PerspectiveTransform, \
12+
srcFrontCamListXY, dstFrontCamListXY
13+
14+
15+
class SubImagePubPointCloudNode:
16+
def __init__(self):
17+
rospy.init_node("image_to_pointcloud_node")
18+
19+
self.bridge = CvBridge()
20+
self.image_sub = rospy.Subscriber("/oak/rgb/image_raw", Image, self.image_callback, queue_size=1)
21+
self.pc_pub = rospy.Publisher("/image_pointcloud", PointCloud2, queue_size=1)
22+
23+
self.latest_msg = None
24+
25+
# 相机参数
26+
self.src_pts = srcFrontCamListXY
27+
self.dst_pts = dstFrontCamListXY
28+
29+
rospy.loginfo("Image to PointCloud2 node started.")
30+
31+
def image_callback(self, msg):
32+
try:
33+
img = self.bridge.imgmsg_to_cv2(msg, desired_encoding='bgr8')
34+
except Exception as e:
35+
rospy.logwarn(f"CV Bridge error: {e}")
36+
return
37+
38+
masked_img = maskCamKeyArea(img, "FRONT")
39+
transformer = PerspectiveTransform(masked_img, self.src_pts.copy(), self.dst_pts.copy())
40+
transformed_img = transformer.getTransformedImg()
41+
42+
pc_msg = cvtImg2PointCloudMsg(
43+
transformed_img,
44+
frameId="base_link",
45+
pixelSize=0.02,
46+
stride=6,
47+
stamp = msg.header.stamp
48+
)
49+
self.pc_pub.publish(pc_msg)
50+
51+
52+
53+
if __name__ == "__main__":
54+
try:
55+
node = SubImagePubPointCloudNode()
56+
rospy.spin()
57+
except rospy.ROSInterruptException:
58+
pass
59+
60+
61+
# TODO : try down-sample

Fusion/TestStitchWithRosbag.py

Whitespace-only changes.

Fusion/document.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
roscore
2+
3+
rosbag play -l ~/Projects/GEMstack/logs/three_02_05_25/vehicle.bag --rate=0.2
4+
5+
rosbag play -l ~/Projects/GEMstack/logs/parking/FirstParking.bag
6+
7+
8+
roslaunch fusion sensors.launch
9+
10+
python3 SubImagePubPointCloudNode.py # publish image2pointcloud topic
11+
12+
13+
14+

Fusion/imagePublisher.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import rospy
2+
from sensor_msgs.msg import Image
3+
from cv_bridge import CvBridge
4+
import cv2
5+
from readImageFromRosbag import readImageFromRosbag # 你已有的函数
6+
7+
8+
def publish_image_loop(image, topic="/oak/rgb/image_raw", frame_id="oak_camera", rate_hz=10):
9+
rospy.init_node("manual_image_publisher", anonymous=True)
10+
bridge = CvBridge()
11+
pub = rospy.Publisher(topic, Image, queue_size=1)
12+
13+
msg = bridge.cv2_to_imgmsg(image, encoding="bgr8")
14+
msg.header.frame_id = frame_id
15+
16+
rate = rospy.Rate(rate_hz)
17+
while not rospy.is_shutdown():
18+
msg.header.stamp = rospy.Time.now()
19+
pub.publish(msg)
20+
rate.sleep()
21+
22+
if __name__ == "__main__":
23+
bag_path = "/your/path/front.bag"
24+
image_topic = "/oak/rgb/image_raw"
25+
26+
# 从 bag 中读取一张图
27+
image = readImageFromRosbag(bag_path, imageTopic=image_topic)
28+
29+
# 循环持续发布该图像
30+
publish_image_loop(image, topic=image_topic, frame_id="oak_camera", rate_hz=10)

0 commit comments

Comments
 (0)