找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

2738

积分

0

好友

398

主题
发表于 前天 23:58 | 查看: 0| 回复: 0

在和漂移驴车社区的朋友们讨论纯视觉车道检测时,关于如何进行成本控制产生了强烈的争执。他们觉得很多人不愿意动辄上千元的高性能主板来搭建驴车环境,比如我们目前使用的 Lattepanda MU 主板就价格昂贵。也有人提出使用 ESP32 配合 ESP32-CAM 来实现。我曾测试过 EPS32-CAM,虽然能用,但要使用 OpenCV 框架实现车道保持,用单片机还真没尝试过。

在反复考虑过程中,树莓派5性能够但太贵,Orange Pi 性能够也不便宜。纠结之时,我突然看到了正在制作的 Open Duck Mini V2 骨架,灵机一动想起了手里的树莓派 CM0。虽然跑大模型有点吃力,但只是跑视觉似乎不是不行,试试也无妨,于是就有了这篇文档。

自制机器人底盘照片

为何不用 Tiny ML?

Tiny ML(微型机器学习)是将机器学习模型部署到资源有限设备上的技术。对于 CM0 这种内存有限的设备,需要使用非常轻量级的模型并优化内存和计算效率。如果采用 Tiny ML,因为很多大佬已经捷足先登,制作了多个基于 ONNX Runtime 推理架构的文档,我再写难免撞车。我本考虑利用小型神经网络进行数字分类任务,使用预训练的 TensorFlow Lite 模型在 CM0 上运行,但感觉大家已抛弃 TensorFlow 拥抱 PyTorch。考虑到设备内存限制,需要在模型和处理上优化,可能对新手来说是调试噩梦。因此,我们先用 OpenCV 最基础的霍夫变换来完成这次任务,开始这个有趣的漂移驴车 CM0 基础(谐音梗)操作之旅。

实现思路

  1. 摄像头输入采集:获取实时视频流并提取每一帧图像。
  2. 图像预处理:对图像进行处理,突出车道线特征。
  3. 车道线检测:利用经典的计算机视觉方法识别车道线。
  4. 实时显示结果:在屏幕上实时显示车道线识别结果。

预设环境

烧录最新的 Raspberry Pi OS 64bit (Trixie),不要使用 Lite 版本,否则没有桌面,OpenCV 无法显示图片,会提示错误。

Qt插件错误终端截图

下面是我系统的状态信息:

系统信息fastfetch输出

各项版本信息如上图所示。

步骤 1: 安装依赖

首先,安装 OpenCV 和其他必要的库来处理图像采集和处理。

sudo apt-get update
sudo apt-get install python3-pip libopencore-amrnb-dev

apt update和安装包截图

pip3 install opencv-python numpy

如果想要避免 PEP 668 问题,必须加 --break-system-packages 参数,否则就构建虚拟环境。这次我本想不构建虚拟环境直接操作,但这样对系统有风险。建议还是创建虚拟环境来运行独立的应用。

pip安装外部管理环境错误

所以按照下面方式进行操作:

sudo apt -y install virtualenv    # 安装virtualenv的包
virtualenv -p python3 lineTracker # 创建虚拟环境
cd lineTracker/
source bin/activate  # 激活环境
pip install opencv-python numpy  # 安装软件包

创建和激活虚拟环境过程

pip安装opencv和numpy成功

成功完成可以看到我们的 NumPy 版本是 2.2.6,OpenCV 的版本是 4.12.0.88。

步骤 2: 摄像头采集

需要通过摄像头采集视频帧。OpenCV 提供了一个简单的接口来打开摄像头并获取图像。这里请出老演员 USB 摄像头:

USB摄像头设备

插入电脑,获取信息。

lsusb

lsusb命令输出摄像头信息

ls /dev/video*

列出视频设备文件

编写一个 linetracker.py 的 Python 文件,写入下面代码:

import cv2

# 打开摄像头
cap = cv2.VideoCapture(0)  # 0表示默认摄像头
if not cap.isOpened():
    print("无法打开摄像头")
    exit()

while True:
    # 逐帧捕获
    ret, frame = cap.read()
    if not ret:
        print("无法获取帧")
        break
    # 显示图像
    cv2.imshow('Camera', frame)
    # 按 'q' 键退出
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break

cap.release()
cv2.destroyAllWindows()

测试如果没有问题就继续,有问题先解决了摄像头识别的问题再继续。

摄像头测试代码与运行

步骤 3: 图像预处理

在进行车道线检测前,通常需要对图像进行预处理,例如转换为灰度图像、去噪、边缘检测等。

def preprocess_image(frame):
    # 转换为灰度图像
    gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
    # 高斯模糊去噪
    blur = cv2.GaussianBlur(gray, (5, 5), 0)
    # Canny边缘检测
    edges = cv2.Canny(blur, 50, 150)
    return edges

步骤 4: 车道线检测

我们可以使用经典的计算机视觉方法(例如霍夫变换)来检测车道线。

def detect_lane_lines(edges, frame):
    # 定义感兴趣区域
    height, width = edges.shape
    mask = np.zeros_like(edges)
    # 三角形区域的顶点
    polygon = np.array([[
        (100, height), (width - 100, height), (width // 2, height // 2)
    ]], np.int32)
    # 创建掩膜
    cv2.fillPoly(mask, polygon, 255)
    masked_edges = cv2.bitwise_and(edges, mask)
    # 霍夫变换检测直线
    lines = cv2.HoughLinesP(masked_edges, 1, np.pi / 180, threshold=100, minLineLength=50, maxLineGap=50)
    # 在图像上绘制检测到的线
    if lines is not None:
        for line in lines:
            x1, y1, x2, y2 = line[0]
            cv2.line(frame, (x1, y1), (x2, y2), (0, 255, 0), 5)
    return frame

步骤 5: 主体代码 — 实时车道线检测

import numpy as np
import cv2

def preprocess_image(frame):
    # 转换为灰度图像
    gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
    # 高斯模糊去噪
    blur = cv2.GaussianBlur(gray, (5, 5), 0)
    # Canny边缘检测
    edges = cv2.Canny(blur, 50, 150)
    return edges

def detect_lane_lines(edges, frame):
    # 定义感兴趣区域
    height, width = edges.shape
    mask = np.zeros_like(edges)
    # 三角形区域的顶点
    polygon = np.array([[
        (100, height), (width - 100, height), (width // 2, height // 2)
    ]], np.int32)
    # 创建掩膜
    cv2.fillPoly(mask, polygon, 255)
    masked_edges = cv2.bitwise_and(edges, mask)
    # 霍夫变换检测直线
    lines = cv2.HoughLinesP(masked_edges, 1, np.pi / 180, threshold=100, minLineLength=50, maxLineGap=50)
    # 在图像上绘制检测到的线
    if lines is not None:
        for line in lines:
            x1, y1, x2, y2 = line[0]
            cv2.line(frame, (x1, y1), (x2, y2), (0, 255, 0), 5)
    return frame

# 打开摄像头
cap = cv2.VideoCapture(0)
if not cap.isOpened():
    print("无法打开摄像头")
    exit()

while True:
    # 逐帧捕获
    ret, frame = cap.read()
    if not ret:
        print("无法获取帧")
        break
    # 预处理图像
    edges = preprocess_image(frame)
    # 检测车道线
    result = detect_lane_lines(edges, frame)
    # 显示结果
    cv2.imshow('Lane Detection', result)
    # 按 'q' 键退出
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break

cap.release()
cv2.destroyAllWindows()

执行后检查一下效果。我为了方便采集图片,使用了 MobaXterm 通过 SSH 远程连接到树莓派 CM0,操作起来更方便,还支持远程通过 XDMCP 查看桌面信息。

SSH远程连接终端

网上找了一段视频,通过摄像头对着视频检测,其实效果没有录制一段高清视频再来检测好,决定回头从我的车载记录仪里面拉点数据来测试。朋友也建议用特斯拉 U 盘的画面训练。

微信聊天记录关于素材

另外,我们漂移驴车的朋友也提供了驴车的训练集数据:

训练集图片文件夹

我把图片合成视频后再播放检测一下,于是上传到 CM0 的目录下用 ffmpeg 合成。

解压数据文件

列出images目录下大量图片文件

图片嘎嘎多,都是实际采集的数据。

单独看一张:

模糊的驴车第一视角图片

文件名有点乱,需要排序并重新命名一下。

乱序的图片文件名列表

ls *.jpg | sort -n | cat -n | while read n f ; do echo $n $f; sleep 1; done

排序并列出文件名

检查如果这样 OK,就开始重命名:

批量重命名命令

文件名改好就这样:

重命名后的前10个文件

合成视频看看:

ffmpeg -framerate 24 -i ./img%04d.jpg -c:v libx264 ../output.mp4

FFmpeg合成视频过程

ffplay ../output.mp4

模糊的播放画面1

终端ffplay信息与播放画面2

看右侧的图,因为要优化它的性能,所以图片的 ROI 区域非常小。这样速度快很多,处理起来也快。处理的视频需要有白色横道线或者黄色横道线,需要颜色对比度比较大,这个视频合成出来我发现地面特征不明显,失败了。

车道检测程序运行界面

下面是我网上找的视频测试结果:

夜间街道车道检测结果

这边我们详细讲解一下这里的关键代码:

# 三角形区域的顶点
polygon = np.array([[ (100, height), (width - 100, height), (width // 2, height // 2) ]], np.int32)
# 创建掩膜
cv2.fillPoly(mask, polygon, 255)
masked_edges = cv2.bitwise_and(edges, mask)
# 霍夫变换检测直线
lines = cv2.HoughLinesP(masked_edges, 1, np.pi / 180, threshold=100, minLineLength=50, maxLineGap=50)

这部分代码的目的是通过使用霍夫变换来从图像中检测车道线。霍夫变换是一种常用于检测图像中直线的算法,尤其适用于图像中存在噪声和其他干扰的情况。在车道线检测中,我们使用霍夫变换来找出图像中的直线,并将其作为车道线。

我将详细解释代码中的每个步骤。

1. 定义感兴趣区域(ROI)

polygon = np.array([[
    (100, height), (width - 100, height), (width // 2, height // 2)
]], np.int32)

这一行代码定义了一个三角形区域,这个区域代表我们感兴趣的区域。我们只关心车道线所在的区域,因此限制检测区域可以提高效率并减少不必要的计算,对于我们的 CM0 来说,相当于减负了。

  • (100, height):这个点是左下角,距离图像左边 100 像素,底边的位置是图像的底部。
  • (width - 100, height):这个点是右下角,距离图像右边 100 像素,同样位于图像底部。
  • (width // 2, height // 2):这个点是三角形的顶部,位于图像的中心上方一半的高度。

这三个点构成了一个三角形,该三角形的大底边位于图像的底部,顶部位于图像的中间。车道线通常出现在图像的底部区域,因此我们通过限制 ROI 来专注于这个区域。

2. 创建掩膜并进行遮罩

cv2.fillPoly(mask, polygon, 255)
masked_edges = cv2.bitwise_and(edges, mask)
  • mask = np.zeros_like(edges):创建一个和边缘图像大小相同的全零矩阵。掩膜是一个黑色的图像,意味着它的所有像素值都是 0。
  • cv2.fillPoly(mask, polygon, 255):用白色填充我们定义的三角形区域,表示我们感兴趣的区域。也就是说,这个操作将在掩膜图像中绘制一个白色三角形,其余部分保持黑色。
  • masked_edges = cv2.bitwise_and(edges, mask):这一步将掩膜应用于边缘图像。cv2.bitwise_and 是逐像素的按位与操作,意思是只有掩膜中白色区域对应的边缘图像部分会被保留,其它部分会被遮盖。最终的 masked_edges 只包含我们感兴趣区域的边缘。

3. 霍夫变换检测直线

lines = cv2.HoughLinesP(masked_edges, 1, np.pi / 180, threshold=100, minLineLength=50, maxLineGap=50)

这行代码使用霍夫变换来检测图像中的直线。cv2.HoughLinesP 是 OpenCV 提供的霍夫变换的实现,它返回一个检测到的线段集合。

  • masked_edges:这是经过掩膜处理后的边缘图像,只包含感兴趣区域的边缘信息。
  • 1:表示霍夫变换的距离分辨率。1 表示图像坐标系中的每个像素单位。可以理解为在极坐标系中测量直线时,半径的分辨率。
  • np.pi / 180:这是霍夫变换中的角度分辨率。np.pi / 180 相当于将角度单位从度转换为弧度。也就是说,霍夫变换中每个角度单位的步长是 1 度。
  • threshold=100:该参数表示只有当直线投票数超过 100 时才会被认为是一条有效的直线。这个阈值控制了检测的灵敏度。投票数越高,检测出的直线越可靠。
  • minLineLength=50:表示一条直线最小的像素长度。只有长度大于或等于 50 像素的线段才会被检测到。这是为了避免检测到很短的噪声线段。
  • maxLineGap=50:表示在两条线段之间允许的最大间隙。意思是如果线段之间的间隙小于 50 像素,它们会被认为是一条连续的线。如果间隙较大,则认为是两条不同的线。
  • cv2.HoughLinesP 函数返回的是一组直线的端点坐标。这些直线对应于检测到的车道线。

4. 绘制检测到的直线

if lines is not None:
    for line in lines:
        x1, y1, x2, y2 = line[0]
        cv2.line(frame, (x1, y1), (x2, y2), (0, 255, 0), 5)
  • if lines is not None:判断是否检测到直线。如果没有检测到任何直线,lines 将是 None,否则它将是一个包含直线端点坐标的数组。
  • for line in lines::遍历检测到的所有直线。每条直线是由两个端点坐标 (x1, y1)(x2, y2) 表示的。
  • cv2.line(frame, (x1, y1), (x2, y2), (0, 255, 0), 5):在原始帧上绘制每条检测到的直线。线条的颜色是绿色,线条的宽度是 5。

通过这些步骤,程序能够在图像中识别并绘制出车道线,帮助识别车辆所在车道。

总体来说,这段代码首先通过定义一个三角形区域来限制车道线检测的范围,然后使用霍夫变换检测图像中的直线,并在图像上绘制这些直线。这样,可以实时地从摄像头采集的图像中识别并标出车道线。这是其中最关键的部分。当我们能够顺利检测车道线后,可以进行下一步操作:判断车是否靠近任何一侧的车道线,如果靠近则认为车辆偏移了,然后通过偏移量变化的值通过 map 函数映射到舵机对应的区间,就可以实现车道保持了。

那么,要实现车辆始终保持在车道中央,并且如果车辆偏离车道中心就进行反向补偿,同时将补偿映射到舵机上,就需要结合车道线检测结果和舵机控制系统来动态调整车辆的转向。

目前我想到的步骤和 AI 提供的思路基本一致:

  1. 计算车道线的中心位置:从图像中检测到的车道线,计算车道线的中心点。
  2. 检测车辆的偏移量:将车辆当前的实际位置与车道中心进行比较,计算偏移量。
  3. 根据偏移量调整舵机控制:根据偏移量来决定舵机控制的方向和角度,执行反向补偿。

步骤 1: 计算车道线的中心位置

首先,我们需要计算车道线的中心位置。在 detect_lane_lines 函数中,提取车道线的端点信息,然后根据这些端点计算车道线的中心。

例如,对于两条车道线,我们可以计算两条线的中点,并将这个中点作为车道的中心。

修改代码:

def detect_lane_lines(edges, frame):
    # 定义感兴趣区域
    height, width = edges.shape
    mask = np.zeros_like(edges)
    # 三角形区域的顶点
    polygon = np.array([[
        (100, height), (width - 100, height), (width // 2, height // 2)
    ]], np.int32)
    # 创建掩膜
    cv2.fillPoly(mask, polygon, 255)
    masked_edges = cv2.bitwise_and(edges, mask)
    # 霍夫变换检测直线
    lines = cv2.HoughLinesP(masked_edges, 1, np.pi / 180, threshold=100, minLineLength=50, maxLineGap=50)

    if lines is not None:
        left_line_points = []
        right_line_points = []
        # 按左右车道线分组
        for line in lines:
            x1, y1, x2, y2 = line[0]
            # 判断直线位置,简单粗暴地通过斜率来判断左右车道线
            slope = (y2 - y1) / (x2 - x1) if x2 != x1 else 0
            if slope > 0:  # 右车道线
                right_line_points.append((x1, y1, x2, y2))
            else:  # 左车道线
                left_line_points.append((x1, y1, x2, y2))
        # 计算左右车道线的中心点
        left_center = np.mean([x1 for x1, y1, x2, y2 in left_line_points], axis=0) if left_line_points else 0
        right_center = np.mean([x2 for x1, y1, x2, y2 in right_line_points], axis=0) if right_line_points else width
        # 计算车道的中心
        lane_center = (left_center + right_center) / 2
        # 绘制中心线
        cv2.line(frame, (int(lane_center), 0), (int(lane_center), height), (0, 0, 255), 5)
        return frame, lane_center
    else:
        return frame, None

步骤 2: 计算车辆的偏移量

现在我们已经有了车道的中心位置。接下来,你需要计算车辆当前所在的位置。这通常可以通过确定图像中车辆所在的位置来实现,或者假设车辆的前部位于图像底部的中间位置。

假设车辆位于图像的下部中央,我们可以将图像底部的中点与车道的中心进行比较,得到偏移量:

def calculate_offset(lane_center, frame_width):
    # 假设车辆位于底部中央,计算偏移量
    vehicle_center = frame_width // 2  # 车辆的当前位置(图像底部中央)
    offset = vehicle_center - lane_center
    return offset

步骤 3: 根据偏移量调整舵机

根据偏移量,我们需要调整舵机的角度。假设偏移量是负的表示车辆偏离车道的左边,偏移量是正的表示车辆偏离车道的右边,我们需要相应地调整舵机。

例如,假设舵机的控制范围是从 -90 度到 +90 度,偏移量越大,舵机转动的角度也越大。实际上我们放在 RC 车辆上舵机的转动范围只要 -30 到 +30 度之间就可以完成转向了,否则转向突然过大,在高速运行的车辆会出现两种情况,一种是直接推头,一种是起漂,如果这时候系统再极速反打方向,那么车辆肯定就漂移了。

def map_to_steering(offset, max_angle=30):
    # 偏移量映射到舵机的角度范围
    steering_angle = offset / 100  # 根据偏移量的大小进行调整
    steering_angle = np.clip(steering_angle, -max_angle, max_angle)  # 限制角度范围
    return steering_angle

步骤 4: 控制舵机

最后,你需要将计算出的舵机角度值传递给舵机控制系统。假设你使用了 PWM 或者其他的硬件接口来控制舵机,可以通过以下代码将舵机角度映射到实际控制信号。

首先,如果你系统上没有 RPi.GPIO,可以通过下面方法安装。

安装RPi.GPIO模块

树莓派GPIO引脚图

我们使用的是 Raspberry Pi 的硬件,可以通过 PWM 信号控制舵机:

import RPi.GPIO as GPIO
import time

# 设置舵机控制引脚
STEERING_PIN = 17  # BCM 命名的17号引脚:如上图所示
GPIO.setmode(GPIO.BCM)
GPIO.setup(STEERING_PIN, GPIO.OUT)

# 初始化PWM信号
pwm = GPIO.PWM(STEERING_PIN, 50)  # 50Hz
pwm.start(7.5)  # 7.5是舵机的中立位置

def control_steering(steering_angle):
    # 将舵机角度映射为PWM占空比
    duty_cycle = 7.5 + steering_angle / 18  # 根据角度映射占空比
    pwm.ChangeDutyCycle(duty_cycle)
    time.sleep(0.1)  # 控制更新频率

然后我们把所有代码整理一下,组合到一起。最终的代码应该是下面这个流程:

车道保持系统流程图

完整代码

import cv2
import numpy as np
import time
import RPi.GPIO as GPIO

# 设置舵机控制引脚
STEERING_PIN = 17
GPIO.setmode(GPIO.BCM)
GPIO.setup(STEERING_PIN, GPIO.OUT)
pwm = GPIO.PWM(STEERING_PIN, 50)  # 50Hz
pwm.start(7.5)

def preprocess_image(frame):
    gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
    blur = cv2.GaussianBlur(gray, (5, 5), 0)
    edges = cv2.Canny(blur, 50, 150)
    return edges

def detect_lane_lines(edges, frame):
    height, width = edges.shape
    mask = np.zeros_like(edges)
    polygon = np.array([[
        (100, height), (width - 100, height), (width // 2, height // 2)
    ]], np.int32)
    cv2.fillPoly(mask, polygon, 255)
    masked_edges = cv2.bitwise_and(edges, mask)
    lines = cv2.HoughLinesP(masked_edges, 1, np.pi / 180, threshold=100, minLineLength=50, maxLineGap=50)
    if lines is not None:
        left_line_points = []
        right_line_points = []
        for line in lines:
            x1, y1, x2, y2 = line[0]
            slope = (y2 - y1) / (x2 - x1) if x2 != x1 else 0
            if slope > 0:
                right_line_points.append((x1, y1, x2, y2))
            else:
                left_line_points.append((x1, y1, x2, y2))
        left_center = np.mean([x1 for x1, y1, x2, y2 in left_line_points], axis=0) if left_line_points else 0
        right_center = np.mean([x2 for x1, y1, x2, y2 in right_line_points], axis=0) if right_line_points else width
        lane_center = (left_center + right_center) / 2
        cv2.line(frame, (int(lane_center), 0), (int(lane_center), height), (0, 0, 255), 5)
        return frame, lane_center
    else:
        return frame, None

def calculate_offset(lane_center, frame_width):
    vehicle_center = frame_width // 2
    offset = vehicle_center - lane_center
    return offset

def map_to_steering(offset, max_angle=30):
    steering_angle = offset / 100
    steering_angle = np.clip(steering_angle, -max_angle, max_angle)
    return steering_angle

def control_steering(steering_angle):
    duty_cycle = 7.5 + steering_angle / 18
    pwm.ChangeDutyCycle(duty_cycle)
    time.sleep(0.1)

cap = cv2.VideoCapture(0)
if not cap.isOpened():
    print("无法打开摄像头")
    exit()

while True:
    ret, frame = cap.read()
    if not ret:
        print("无法获取帧")
        break
    edges = preprocess_image(frame)
    result, lane_center = detect_lane_lines(edges, frame)
    if lane_center is not None:
        offset = calculate_offset(lane_center, frame.shape[1])
        steering_angle = map_to_steering(offset)
        control_steering(steering_angle)
    cv2.imshow('Lane Detection', result)
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break

cap.release()
cv2.destroyAllWindows()
pwm.stop()
GPIO.cleanup()

执行的时候,直接 python linetracker.py 就行。不过得先装车。

准备装车的机器人底盘

组装好的遥控车内部

二楼板已经填满,得再打印一个支架。

遥控车内部电池和电路板

另外准备拆一台美嘉欣也试试看:

美嘉欣遥控车模型1

美嘉欣遥控车模型2

拆车前先拍照留念,预计下一期会展示出来哈哈。

卡通机器人举杯图片

最后,关于优化和性能

由于 CM0 的设备内存有限,可能会出现无法实时处理高分辨率图像的情况。因此,优化计算非常重要。你可以考虑:

  • 减少输入图像分辨率:例如将图像分辨率降低到 320x240 或 640x480。
  • 使用 TensorFlow Lite 模型:如果你想使用深度学习模型来代替经典方法,可以考虑使用轻量级的 TensorFlow Lite 模型进行推理,这也是我准备下次分享的内容之一。

优化代码:

  1. 减少计算量:通过对输入图像进行缩放、降采样等操作减少计算负载。
  2. 减少图像处理步骤:根据需要调整预处理步骤,避免做过多的操作。
  3. 使用硬件加速:如果设备支持硬件加速,可以加速计算。最近有朋友提供了 LLM 8850 那个 AI Hat,遗憾的是 CM0 不支持 PCIe 接口,这个 HAT 无法使用。不然可以试试这个 HAT,或者使用树莓派官方的 AI HAT,目前已经有第二版本的 HAT,据说有 40TOPs 的 int8 推理能力,目前没有拿到设备还无法评测。

总结

这个 Demo 实现了一个实时的车道线检测应用,使用了 OpenCV 中的基本图像处理和经典计算机视觉技术。你可以根据需要调整图像处理的细节,使用轻量级的深度学习模型来提高准确性,也可以进一步优化代码以适应你的设备。另外,装一台车实在麻烦,太多细节。敬请期待下一期的精彩哈哈!




上一篇:SpringBoot SpEL表达式实现灵活权限控制方案详解
下一篇:微服务治理深度解析:从Spring Cloud组件到架构本质的认知跃迁
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-1-24 00:33 , Processed in 0.359368 second(s), 42 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表