1. 从零开始:为什么选择MediaPipe?

如果你正在寻找一个能快速实现人体姿态和手势识别的工具,但又不想在复杂的模型训练和部署上耗费几个月时间,那MediaPipe绝对是你的菜。我第一次接触MediaPipe,也是因为一个紧急的项目需求,需要在两周内做出一个手势控制的演示原型。当时试过几个开源方案,要么配置复杂到让人崩溃,要么在普通电脑上跑起来像幻灯片。直到用了MediaPipe,我才发现原来搞计算机视觉可以这么“偷懒”。

简单来说,MediaPipe是谷歌开源的一个跨平台多媒体机器学习模型应用框架。这个名字听起来有点唬人,但你可以把它理解为一个“工具箱”。这个工具箱里已经打包好了很多现成的、训练好的模型,比如检测人体33个骨骼关键点的姿态模型、识别单只手21个关节的手势模型,甚至还有能同时处理面部、双手和身体姿态的“全家桶”模型(Holistic)。你不需要懂模型内部的复杂数学,也不用准备海量的训练数据,直接调用它的Python接口,几行代码就能看到效果。

它最大的两个优势,我觉得一是,二是省心。快,是因为它对移动设备和普通CPU做了大量优化,在主流配置的笔记本上,处理视频流做到实时(比如每秒30帧)是轻轻松松的。省心,体现在它的“开箱即用”上。环境配置简单,API设计直观,官方提供了大量清晰的示例。对于开发者,尤其是想快速验证想法、搭建Demo或者开发轻量级应用的你来说,它能帮你省下大量前期投入的时间,让你专注于业务逻辑本身。

所以,无论你是想做一个体感游戏、一个智能健身教练、一个手语翻译的雏形,还是仅仅对姿态识别技术感到好奇,MediaPipe都是一个绝佳的起点。接下来,我就带你亲手走一遍流程,从环境搭建到写出第一个能跑的程序,再到如何调整参数优化效果,把踩过的坑和总结的经验都分享给你。

2. 5分钟搞定开发环境与核心概念

工欲善其事,必先利其器。用MediaPipe写Python代码,准备工作其实非常简单。你只需要一个Python环境(我强烈推荐用3.7-3.9版本,兼容性最好),然后用pip安装两个库就齐活了。

打开你的命令行终端(Windows上是CMD或PowerShell,Mac或Linux上是Terminal),输入下面两行命令:

pip install opencv-python
pip install mediapipe

如果安装速度慢,可以临时换成国内的镜像源,比如清华的源,命令是 pip install mediapipe -i https://pypi.tuna.tsinghua.edu.cn/simple。安装完成后,你可以创建一个新的Python文件,比如叫 test_mediapipe.py,先写两行导入代码测试一下:

import cv2
import mediapipe as mp
print(“MediaPipe version:”, mp.__version__)

运行没报错,就说明环境妥了。这里你会接触到MediaPipe最核心的两个模块:solutionsdrawing_utilssolutions 里存放着所有预置的解决方案,比如 hands, pose, holistic,你可以把它们看作不同的“工具”。而 drawing_utils 是一个绘图工具,专门负责把模型识别出的那些关键点(Landmarks)和它们之间的连接线,漂亮地画在图片或视频上。

在深入代码前,咱们先聊聊MediaPipe处理问题的基本逻辑,这能帮你更好地理解后面的参数。它采用的是管道(Pipeline)模式。想象一下工厂的流水线:一帧图像数据进来,先经过预处理(比如调整颜色格式),然后送入具体的检测模型,模型输出一堆关键点的坐标信息,最后这些信息被后处理并绘制出来。整个过程被封装得非常完整,你只需要关心输入和输出。

关键点(Landmark)是核心数据。每个点都是一个三维坐标(x, y, z),其中x和y是归一化后的坐标(取值0到1之间),分别代表在图像宽度和高度上的比例位置,z表示深度信息(离摄像头远近)。比如,鼻子关键点的x坐标是0.5,就代表鼻子在图片水平方向的正中央。理解这一点,对于你后续根据这些坐标做自己的业务计算(比如计算抬手的角度)至关重要。

3. 实战第一步:用图片测试人体姿态估计

好了,理论说再多不如动手试一次。咱们就从最简单的开始:用一张静态图片,检测里面人的身体姿态。我准备了一张名为 person.jpg 的图片放在项目文件夹里,你可以用任何包含清晰人像的图片。

下面这段代码,我逐段给你解释:

import cv2
import mediapipe as mp

# 1. 初始化绘图工具和模型
mp_drawing = mp.solutions.drawing_utils
mp_pose = mp.solutions.pose

# 2. 创建姿态估计实例,这是关键!
pose = mp_pose.Pose(
    static_image_mode=True,  # 因为是静态图片,设为True
    model_complexity=1,       # 模型复杂度:0轻量,1标准,2高精度(更慢)
    min_detection_confidence=0.5  # 检测置信度阈值,低于此值认为未检测到
)

# 3. 读取并处理图片
image = cv2.imread('person.jpg')
if image is None:
    print("图片读取失败,请检查路径!")
    exit()

# MediaPipe模型需要RGB格式的输入,但OpenCV默认读入是BGR格式,所以必须转换
image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)

# 4. 核心处理:将图像送入模型
results = pose.process(image_rgb)

# 5. 看看我们得到了什么
if results.pose_landmarks:
    print(f"成功检测到人体姿态!共 {len(results.pose_landmarks.landmark)} 个关键点。")
    # 举个例子:获取鼻子关键点的实际像素坐标
    h, w, _ = image.shape
    nose = results.pose_landmarks.landmark[mp_pose.PoseLandmark.NOSE]
    nose_pixel_x = int(nose.x * w)
    nose_pixel_y = int(nose.y * h)
    print(f"鼻子在图片中的像素坐标:({nose_pixel_x}, {nose_pixel_y})")
else:
    print("未检测到人体姿态。")

# 6. 把检测到的骨骼点画到原图上
if results.pose_landmarks:
    # 自定义绘制样式:关键点用绿色圆点,连接线用红色细线
    point_spec = mp_drawing.DrawingSpec(color=(0, 255, 0), thickness=2, circle_radius=3)
    line_spec = mp_drawing.DrawingSpec(color=(0, 0, 255), thickness=2)
    mp_drawing.draw_landmarks(
        image,
        results.pose_landmarks,
        mp_pose.POSE_CONNECTIONS,  # 这个常量定义了哪些点之间需要连线
        point_spec,
        line_spec
    )

# 7. 保存并显示结果
cv2.imwrite('person_with_pose.jpg', image)
print("结果已保存为 'person_with_pose.jpg'")
# 如果想弹窗查看,可以取消下面两行的注释
# cv2.imshow('Pose Detection', image)
# cv2.waitKey(0)

# 8. 释放资源(好习惯)
pose.close()

跑完这段代码,你会在文件夹里看到一张新图片,上面的人体被画上了清晰的骨骼连线。这里有几个我踩过坑的细节想强调一下:首先是颜色空间转换cv2.cvtColor 这一步绝对不能省,模型认的是RGB,OpenCV默认是BGR,不转换的话检测会失败或者结果很奇怪。其次是 static_image_mode 参数,处理图片时设为 True,模型会对每一张图都做完整的检测;而在处理视频时,我们会设为 False,这样模型会利用前后帧的信息进行跟踪,速度更快。

model_complexity 这个参数值得多试试。如果图片中的人动作简单,或者你对速度要求极高,可以设为0。如果人物被部分遮挡或者姿势复杂,可以尝试设为2,但速度会明显下降。min_detection_confidence 是过滤弱检测结果的,如果你发现有些明显的姿势没检测出来,可以适当调低这个值(比如到0.3),但同时可能会引入一些误检,需要自己权衡。

4. 让模型动起来:处理视频流与摄像头输入

图片检测成功了,但MediaPipe真正的威力在于处理动态视频。无论是处理已有的视频文件,还是实时读取摄像头画面,原理都是类似的:在一个循环里,逐帧处理。我们来写一个从摄像头实时捕捉并检测姿态的程序。

import cv2
import mediapipe as mp

mp_drawing = mp.solutions.drawing_utils
mp_pose = mp.solutions.pose

# 初始化Pose模型。注意:这里 static_image_mode=False,启用跟踪模式
pose = mp_pose.Pose(
    static_image_mode=False,
    model_complexity=1,
    min_detection_confidence=0.5,
    min_tracking_confidence=0.5  # 跟踪置信度阈值,对视频很重要
)

# 打开默认摄像头(通常是0号设备)
cap = cv2.VideoCapture(0)
if not cap.isOpened():
    print("无法打开摄像头")
    exit()

print("按 'q' 键退出程序...")
while cap.isOpened():
    # 读取一帧
    success, frame = cap.read()
    if not success:
        print("无法获取视频帧,正在退出...")
        break

    # 为了提升性能,可以在这里缩放图像帧。但注意:模型输入尺寸是固定的,内部会处理。
    # frame = cv2.resize(frame, (640, 480))

    # 同样的颜色转换
    frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
    # 为了更好的性能,可以标记图像为不可写(某些环境下能加速)
    frame_rgb.flags.writeable = False

    # 处理帧
    results = pose.process(frame_rgb)

    # 处理完后,再把图像标记为可写,准备绘图
    frame_rgb.flags.writeable = True
    frame_bgr = cv2.cvtColor(frame_rgb, cv2.COLOR_RGB2BGR) # 转回BGR用于OpenCV显示

    # 绘制姿态关键点
    if results.pose_landmarks:
        mp_drawing.draw_landmarks(
            frame_bgr,
            results.pose_landmarks,
            mp_pose.POSE_CONNECTIONS
        )
        # 实时显示某个关键点坐标(例如左手腕)
        h, w, _ = frame_bgr.shape
        left_wrist = results.pose_landmarks.landmark[mp_pose.PoseLandmark.LEFT_WRIST]
        cv2.putText(frame_bgr, f"LW:({left_wrist.x:.2f},{left_wrist.y:.2f})",
                    (50, 50), cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 255, 0), 2)

    # 显示结果
    cv2.imshow('Real-Time Pose Detection', frame_bgr)

    # 监听按键,按'q'退出循环
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break

# 释放所有资源
cap.release()
cv2.destroyAllWindows()
pose.close()

运行这个脚本,你的摄像头应该会打开,你对着摄像头做动作,屏幕上就会实时画出你的骨骼。这里和图片处理有几个关键区别:第一是 static_image_mode=False,这告诉模型这是连续的视频流,它会先进行检测,然后在后续帧中优先使用跟踪算法,这比每帧都重新检测要快得多。第二是多了个 min_tracking_confidence 参数,当跟踪置信度低于这个阈值时,模型会认为跟丢了,自动切换回检测模式。

你可能遇到的问题是画面卡顿。如果觉得帧率低,有几个优化技巧:一是缩小输入图像的尺寸,比如用 cv2.resize 把帧缩放到640x480;二是可以尝试降低 model_complexity 到0;三是确保你的代码里没有不必要的操作(比如每帧都保存图片)。另外,在循环里,把图像标记为不可写(frame_rgb.flags.writeable = False)是一个MediaPipe官方推荐的小技巧,在某些Python环境下能避免内存拷贝,提升速度。

5. 精准捕捉手势:手部关键点检测详解

手势识别是MediaPipe另一个杀手级功能。它能检测出单只手上21个精细的关键点,从手腕到每个手指的指尖、指关节,一清二楚。这为识别数字、手势命令(如OK、胜利手势)提供了基础。我们先从图片检测开始。

import cv2
import mediapipe as mp

mp_drawing = mp.solutions.drawing_utils
mp_hands = mp.solutions.hands

# 初始化手部模型
hands = mp_hands.Hands(
    static_image_mode=True,
    max_num_hands=2,  # 最多检测2只手
    min_detection_confidence=0.5,
    min_tracking_confidence=0.5
)

image = cv2.imread('hand.jpg')  # 准备一张有手的图片
image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
results = hands.process(image_rgb)

print(f"检测到手部数量:{len(results.multi_hand_landmarks) if results.multi_hand_landmarks else 0}")

# 注意:结果结构变了!是多手列表。
if results.multi_hand_landmarks:
    for hand_landmarks in results.multi_hand_landmarks:
        # 绘制单只手的关键点和连线
        mp_drawing.draw_landmarks(
            image,
            hand_landmarks,
            mp_hands.HAND_CONNECTIONS,
            mp_drawing.DrawingSpec(color=(80, 110, 10), thickness=2, circle_radius=3),  # 点样式
            mp_drawing.DrawingSpec(color=(80, 256, 121), thickness=2)  # 线样式
        )
        # 获取这只手是左手还是右手(注意:这是模型预测的,面对摄像头时左右是反的)
        # 需要从 multi_handedness 里获取,这里先绘制,后面详细讲判断逻辑。

cv2.imwrite('hand_detected.jpg', image)
hands.close()

手部检测的结果结构和姿态检测不同,因为可能有多只手。results.multi_hand_landmarks 是一个列表,里面的每个元素 hand_landmarks 对应一只手。results.multi_handedness 则是一个包含分类信息(左手/右手)的列表,顺序与 multi_hand_landmarks 对应。

这里有个非常重要的点:模型判断的左右手,是基于图像本身的左右。也就是说,当你面对摄像头举起你的右手,在摄像头拍到的画面里,这只手位于图像的左侧,模型很可能会将其判断为“左手”。这是所有基于2D图像的手部检测模型都会遇到的镜像问题。在实际应用中,如果需要区分真实的左右手,你可能需要结合身体姿态(如通过Holistic模型知道身体朝向)或者通过交互逻辑来判断(例如先让用户做一个特定手势来校准)。

接下来是视频流的手势检测,逻辑和姿态检测类似,但需要处理多只手:

import cv2
import mediapipe as mp

mp_drawing = mp.solutions.drawing_utils
mp_hands = mp.solutions.hands

hands = mp_hands.Hands(
    static_image_mode=False,
    max_num_hands=2,
    min_detection_confidence=0.5,
    min_tracking_confidence=0.5
)

cap = cv2.VideoCapture(0)

while cap.isOpened():
    success, frame = cap.read()
    if not success:
        break
    frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
    frame_rgb.flags.writeable = False
    results = hands.process(frame_rgb)
    frame_rgb.flags.writeable = True
    frame_bgr = cv2.cvtColor(frame_rgb, cv2.COLOR_RGB2BGR)

    if results.multi_hand_landmarks:
        for idx, hand_landmarks in enumerate(results.multi_hand_landmarks):
            # 绘制
            mp_drawing.draw_landmarks(frame_bgr, hand_landmarks, mp_hands.HAND_CONNECTIONS)
            # 尝试获取并显示左右手信息
            if results.multi_handedness:
                handedness = results.multi_handedness[idx]
                hand_label = handedness.classification[0].label  # 'Left' or 'Right'
                confidence = handedness.classification[0].score
                # 获取手腕根部坐标作为文本显示位置
                h, w, _ = frame_bgr.shape
                cx, cy = int(hand_landmarks.landmark[0].x * w), int(hand_landmarks.landmark[0].y * h)
                cv2.putText(frame_bgr, f"{hand_label}:{confidence:.2f}", (cx-50, cy-20),
                            cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 255, 255), 2)

    cv2.imshow('Hand Tracking', frame_bgr)
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break

cap.release()
cv2.destroyAllWindows()
hands.close()

现在,你的程序不仅能实时框出手,还能标出模型预测的左右手信息。你可以挥挥手,看看这个预测是否准确,体会一下我刚才说的镜像问题。

6. 功能全家桶:Holistic模型同时搞定脸、手、身体

有时候我们需要同时获取人脸、双手和身体的姿态,比如做一个全身的虚拟形象驱动。如果分别调用三个模型,计算量太大。MediaPipe提供了一个“一体化”解决方案——Holistic模型。它一次性输出面部468个点、每只手21个点、身体33个点,而且内部做了优化,效率比分别调用高很多。

import cv2
import mediapipe as mp

mp_drawing = mp.solutions.drawing_utils
mp_holistic = mp.solutions.holistic

# 初始化Holistic模型
holistic = mp_holistic.Holistic(
    static_image_mode=False,
    model_complexity=1,
    min_detection_confidence=0.5,
    min_tracking_confidence=0.5
)

cap = cv2.VideoCapture(0)

while cap.isOpened():
    success, frame = cap.read()
    if not success:
        break
    frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
    frame_rgb.flags.writeable = False
    results = holistic.process(frame_rgb)
    frame_rgb.flags.writeable = True
    frame_bgr = cv2.cvtColor(frame_rgb, cv2.COLOR_RGB2BGR)

    # 1. 绘制面部网格和轮廓(注意:FACE_CONNECTIONS已弃用,新版用FACE_TESSELATION)
    if results.face_landmarks:
        # 使用更精细的面部网格绘制
        mp_drawing.draw_landmarks(
            frame_bgr,
            results.face_landmarks,
            mp_holistic.FACEMESH_TESSELATION,  # 新版API
            landmark_drawing_spec=None,  # 不单独绘制面部点,太多太乱
            connection_drawing_spec=mp_drawing.DrawingSpec(color=(180, 180, 180), thickness=1)
        )

    # 2. 绘制双手
    if results.left_hand_landmarks:
        mp_drawing.draw_landmarks(
            frame_bgr,
            results.left_hand_landmarks,
            mp_holistic.HAND_CONNECTIONS,
            mp_drawing.DrawingSpec(color=(121, 22, 76), thickness=2, circle_radius=3),
            mp_drawing.DrawingSpec(color=(121, 44, 250), thickness=2)
        )
    if results.right_hand_landmarks:
        mp_drawing.draw_landmarks(
            frame_bgr,
            results.right_hand_landmarks,
            mp_holistic.HAND_CONNECTIONS,
            mp_drawing.DrawingSpec(color=(245, 117, 66), thickness=2, circle_radius=3),
            mp_drawing.DrawingSpec(color=(245, 66, 230), thickness=2)
        )

    # 3. 绘制身体姿态
    if results.pose_landmarks:
        mp_drawing.draw_landmarks(
            frame_bgr,
            results.pose_landmarks,
            mp_holistic.POSE_CONNECTIONS,
            mp_drawing.DrawingSpec(color=(80, 22, 10), thickness=3, circle_radius=4),
            mp_drawing.DrawingSpec(color=(80, 44, 121), thickness=3)
        )

    cv2.imshow('Holistic Full Body Tracking', frame_bgr)
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break

holistic.close()
cap.release()
cv2.destroyAllWindows()

运行这段代码,你会看到一个“全身透视”的效果。Holistic模型非常强大,但同时对计算资源的要求也更高。如果你的应用场景不需要面部细节,只关心身体和手,那么分别使用Pose和Hands模型可能效率更高。另外要注意API的版本,我在这里使用了 FACEMESH_TESSELATION 来绘制面部,这是新版本推荐的方式,画出来是精细的三角网格,而不是简单的轮廓线。

7. 进阶技巧:参数调优与常见问题排坑

用熟了基本功能后,你肯定会想让它更贴合自己的项目。这里分享几个我实践中总结的进阶技巧和常见坑的解决办法。

性能调优参数表:

参数 适用模型 推荐值范围 作用与影响
static_image_mode 所有 True / False 核心参数。处理图片用True,视频流用False以启用跟踪。
model_complexity Pose, Holistic 0, 1, 2 复杂度越高精度越好,速度越慢。视频实时应用建议0或1。
min_detection_confidence 所有 0.3 ~ 0.7 检测阶段置信度阈值。过低易误检,过高可能漏检。
min_tracking_confidence 所有(视频模式) 0.3 ~ 0.7 跟踪阶段置信度阈值。影响模型在“检测”与“跟踪”间切换。
max_num_hands Hands 1, 2 最多检测的手的数量。设为2会略微增加计算量。
smooth_landmarks Pose, Holistic True / False 平滑关键点,减少抖动。视频模式下建议开启。

常见问题与解决方案:

  1. 检测不到或时有时无:首先检查输入图像格式是否为RGB。然后尝试降低 min_detection_confidence。如果背景复杂或光照太暗,可以尝试在将图像送入模型前,用OpenCV做一下简单的预处理,比如提高对比度(cv2.convertScaleAbs)或进行高斯模糊降噪(cv2.GaussianBlur)。

  2. 关键点抖动严重:在视频模式下,确保 static_image_mode=False 并且 smooth_landmarks=True。可以适当提高 min_tracking_confidence,让模型更倾向于使用稳定的跟踪结果,而不是频繁重新检测。你也可以在自己代码里对连续帧的坐标做平滑滤波,比如使用简单移动平均。

  3. CPU占用率100%,风扇狂转:这是MediaPipe在全力工作的表现。如果想省电或降低发热,最有效的方法是降低输入分辨率(如320x240)和 model_complexity。也可以限制处理帧率,比如每两帧处理一帧(在循环里用一个计数器判断)。

  4. 如何获取三维深度信息(Z坐标):关键点的 .z 属性就是深度信息,但要注意,这个值是以髋部中间点为参考原点的相对深度,单位不是物理距离(如米)。它的意义在于比较不同关键点之间的相对远近。例如,比较鼻子和左肩的z值,可以判断头部的转向。

  5. 想自定义关键点连线怎么办draw_landmarks 函数里的 connections 参数(如 mp_pose.POSE_CONNECTIONS)本身就是一个预定义的列表。你可以完全不用它,自己创建一个 list,里面每个元素是一个 (start_index, end_index) 的元组,指定你想连接哪些点。这在你需要突出显示特定骨骼时非常有用。

8. 从Demo到应用:两个实用项目思路

掌握了基础,我们就可以想想怎么用它做点有意思的东西了。这里抛砖引玉,分享两个我做过或构思过的项目思路,你可以基于它们拓展。

项目一:简易健身动作计数器 比如做一个深蹲计数器。原理是:利用Pose模型获取髋部(左右髋关键点)和膝盖(左右膝关键点)的坐标。计算髋部中点与膝盖中点在垂直方向上的距离。当这个距离小于一个阈值(表示蹲下),并且持续若干帧后,再恢复到大于阈值(表示站起),就计为完成一次深蹲。

# 伪代码逻辑
hip_center_y = (left_hip.y + right_hip.y) / 2
knee_center_y = (left_knee.y + right_knee.y) / 2
distance = abs(hip_center_y - knee_center_y)

if distance < THRESHOLD_LOW and not is_squatting:
    is_squatting = True
elif distance > THRESHOLD_HIGH and is_squatting:
    is_squatting = False
    squat_count += 1
    print(f"深蹲次数:{squat_count}")

你可以加上语音提示,或者在画面上用大字显示计数,一个简单的健身辅助工具就出来了。

项目二:静态手势识别控制器 识别几个简单手势来控制电脑,比如“五指张开”暂停播放,“握拳”继续播放,“食指伸出”音量增加。原理是:利用Hands模型的关键点,计算特定指尖(如食指指尖 INDEX_FINGER_TIP)到手掌根部(WRIST)的距离,或者计算所有指尖是否都远离掌心。

# 伪代码:检测“五指张开”
def is_hand_open(landmarks):
    wrist = landmarks[mp_hands.HandLandmark.WRIST]
    fingertips = [mp_hands.HandLandmark.THUMB_TIP, INDEX_FINGER_TIP, ...]
    distances = []
    for tip in fingertips:
        dist = calc_distance(landmarks[tip], wrist)
        distances.append(dist)
    # 如果所有指尖离手腕都足够远,认为是张开的手
    return all(d > OPEN_THRESHOLD for d in distances)

结合Python的 pyautogui 库模拟键盘按键,就能实现手势控制播放器了。这个过程中,阈值(OPEN_THRESHOLD)需要你根据自己的摄像头距离和手的大小进行校准测试。

我在实际做这些项目时,最大的体会就是:先让基础流程跑通,再逐步增加逻辑。不要一开始就想着做一个完美无缺的系统。先用MediaPipe把关键点数据稳定地拿到手,打印出来看看规律,然后再设计你的识别逻辑。遇到识别不准的情况,多从光照、背景和摄像头角度上找原因,往往比一味调整代码参数更有效。MediaPipe是一个强大的工具,但它不是魔法,理解和适应它的特性,才能让它更好地为你服务。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐