目录

  1. 导论
  2. OpenCV 基础知识
  3. 图像处理基础
  4. 特征提取与描述
  5. 目标检测与跟踪
  6. 摄像头与视频处理
  7. 深度学习与 OpenCV
  8. 项目实战
  9. 性能优化与部署
  10. 未来趋势与发展

1. 导论

1.1 计算机视觉的基本概念

计算机视觉(Computer Vision, CV)是一门致力于赋予机器“视觉”能力的交叉学科。它融合了图像处理、模式识别、机器学习、人工智能等多个领域的技术,使计算机能够从图像或视频中“看懂”并理解信息。其核心目标涵盖识别、分类、检测、跟踪、重建以及更深层次的场景理解。这些能力的应用已渗透到自动驾驶、医疗影像分析、安防监控、工业自动化等广泛领域,正在深刻地改变着我们的世界。

1.2 OpenCV 的背景与目标

OpenCV (Open Source Computer Vision Library) 是计算机视觉和机器学习领域最著名、最广泛使用的开源库。自1999年问世以来,它凭借其跨平台性(支持Windows, Linux, macOS, Android, iOS等)、高效的算法实现(尤其在C++版本中高度优化)以及多语言接口(Python, C++, Java),极大地降低了计算机视觉技术的开发门槛。OpenCV的目标是提供一套全面、易用的工具集,让开发者能够快速构建出高性能的计算机视觉应用。本指南将引导您掌握这一强大工具,从基础到实战。


2. OpenCV 基础知识

2.1 安装和配置 OpenCV

2.1.1 Python 环境

对于Python开发者而言,使用pip是最便捷的安装方式。

# 安装最新稳定版 OpenCV
pip install opencv-python

# 如果需要包含额外contrib模块的完整版(通常无需)
pip install opencv-contrib-python

若使用Anaconda/Miniconda,可通过conda安装:

conda install -c conda-forge opencv

2.1.2 C++ 环境

C++环境的安装通常涉及以下方式:

  • 预编译二进制文件:从OpenCV官网下载对应操作系统的安装包,并在IDE(如Visual Studio, CLion)中手动配置项目属性(头文件路径、库文件路径、链接器输入)。
  • 源码编译:下载OpenCV源码,使用CMake工具进行配置,再通过C++编译器(如g++, MSVC)进行编译和安装。此方式灵活性最高,可按需定制。

2.1.3 验证安装

安装完成后,在Python交互环境中执行:

import cv2
print(cv2.__version__)

若输出版本号,则表示安装成功。

2.2 OpenCV 的基本数据结构和函数

OpenCV在Python中主要使用NumPy数组来表示图像。

  • 图像表示

    • numpy.ndarray:这是OpenCV处理图像的核心数据结构。
      • 灰度图像:二维数组 (height, width)
      • 彩色图像(BGR):三维数组 (height, width, channels),通道数为3。
    • 像素值:通常为 uint8(0-255),也可使用 float32 等。
  • 核心函数

    • cv2.imread(filename, flags):读取图像。
    • cv2.imshow(winname, mat):显示图像。
    • cv2.waitKey(delay):等待键盘输入(delay为毫秒,0表示无限等待)。
    • cv2.destroyAllWindows():关闭所有OpenCV窗口。
    • cv2.imwrite(filename, mat):保存图像。
    • cv2.cvtColor(src, code):颜色空间转换。
    • cv2.GaussianBlur(src, ksize, sigmaX):高斯模糊。

2.3 图像读取、显示和保存

以下Python代码演示了基础的图像操作:

import cv2
import numpy as np

# --- 图像读取 ---
# 确保 'input_image.jpg' 文件存在于脚本同目录,或提供完整路径
# cv2.IMREAD_COLOR (默认): 加载彩色图,忽略alpha通道
# cv2.IMREAD_GRAYSCALE: 加载灰度图
# cv2.IMREAD_UNCHANGED: 加载原图,包括alpha通道
img = cv2.imread('input_image.jpg', cv2.IMREAD_COLOR)

if img is None:
    print("错误:无法读取图像。请检查文件路径和文件名。")
else:
    # --- 图像显示 ---
    cv2.imshow('Image Display', img)
    print("请按任意键关闭图像显示窗口...")
    cv2.waitKey(0) # 等待用户按键,0表示无限等待
    cv2.destroyAllWindows() # 销毁所有OpenCV创建的窗口

    # --- 图像保存 ---
    success = cv2.imwrite('output_image.png', img)
    if success:
        print("图像已成功保存为 output_image.png")
    else:
        print("错误:图像保存失败。")

注意:运行前请准备好名为 input_image.jpg 的图像文件。


3. 图像处理基础

3.1 灰度转换和颜色空间

颜色空间定义了如何表示颜色。OpenCV默认使用BGR(蓝、绿、红)顺序,而非常见的RGB。

  • BGR/RGB:标准颜色表示。
  • 灰度 (Grayscale):仅包含亮度信息,是很多后续处理(如边缘检测)的基础。
  • HSV/HLS:由色调(Hue)、饱和度(Saturation)、明度(Value)/亮度(Lightness)组成,分离了颜色与亮度信息,便于颜色分析。

转换到灰度图可显著简化处理:

import cv2

# 假设 img 已经成功读取
if img is not None:
    # BGR 转 灰度
    gray_img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    cv2.imshow('Original Image', img)
    cv2.imshow('Grayscale Image', gray_img)
    print("按任意键继续...")
    cv2.waitKey(0)
    cv2.destroyAllWindows()

    # BGR 转 HSV
    hsv_img = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
    cv2.imshow('HSV Image', hsv_img)
    print("按任意键关闭HSV图像窗口...")
    cv2.waitKey(0)
    cv2.destroyAllWindows()

3.2 图像平滑和滤波

平滑(滤波)是去除图像噪声、减少细节,以便后续处理更稳定的技术。

  • 均值滤波:用邻域像素的平均值替换中心像素,简单但可能模糊边缘。
  • 高斯滤波:使用高斯核卷积,对中心像素赋予更高权重,模糊效果更自然,是常用方法。
  • 中值滤波:用邻域像素的中值替换中心像素,对椒盐噪声(随机黑白点)效果尤佳,且能较好保留边缘。
import cv2
import numpy as np

# 假设 img 已经成功读取
if img is not None:
    # --- 高斯滤波 ---
    # ksize: 核的大小(宽, 高),必须是奇数。如 (5, 5)。
    # sigmaX: X方向高斯核标准差,0表示自动计算。
    blurred_img_gaussian = cv2.GaussianBlur(img, (5, 5), 0)

    # --- 中值滤波 ---
    # ksize: 核的大小,必须是奇数。
    blurred_img_median = cv2.medianBlur(img, 5)

    cv2.imshow('Original', img)
    cv2.imshow('Gaussian Blur', blurred_img_gaussian)
    cv2.imshow('Median Blur', blurred_img_median)
    print("按任意键关闭平滑处理窗口...")
    cv2.waitKey(0)
    cv2.destroyAllWindows()

3.3 边缘检测

边缘是图像中亮度快速变化的地方,常对应于物体的轮廓。边缘检测算法提取这些结构信息。

  • Sobel 算子:计算图像在X、Y方向的梯度。
  • Laplacian 算子:计算二阶导数,对亮度变化敏感。
  • Canny 边缘检测算法:一种多阶段算法,包括噪声抑制、梯度计算、非极大值抑制和滞后阈值处理。它是目前最常用且效果较好的边缘检测方法。
import cv2

# 假设 img 已经成功读取
if img is not None:
    # Canny 通常在灰度图上进行
    gray_img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

    # --- Canny 边缘检测 ---
    # threshold1: 低阈值
    # threshold2: 高阈值
    # Canny 使用滞后阈值法,高于高阈值的一定是边缘,低于低阈值的一定不是,中间的则取决于是否连接到高阈值边缘。
    canny_edges = cv2.Canny(gray_img, 100, 200)

    cv2.imshow('Original Grayscale', gray_img)
    cv2.imshow('Canny Edges', canny_edges)
    print("按任意键关闭边缘检测窗口...")
    cv2.waitKey(0)
    cv2.destroyAllWindows()

4. 特征提取与描述

特征点是图像中对尺度、旋转、光照等变化具有鲁棒性的关键点,特征描述符则用来刻画这些点周围的局部图像信息。

4.1 Harris 角点检测

角点(Corner)是图像中具有两个或多个边缘交汇的点,对图像变换非常稳定。

import cv2
import numpy as np

# 假设 img 已经成功读取
if img is not None:
    gray_img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

    # --- Harris 角点检测 ---
    # blockSize: 计算Harris响应时使用的邻域大小。
    # ksize: Sobel导数核的大小。
    # k: Harris检测器中的自由参数,通常在0.04到0.06之间。
    dst = cv2.cornerHarris(gray_img, blockSize=2, ksize=3, k=0.04)

    # 将响应值大于阈值的点标记为角点
    img_dst = cv2.cvtColor(gray_img, cv2.COLOR_GRAY2BGR) # 转换为彩色以便绘制
    img_dst[dst > 0.01 * dst.max()] = [0, 0, 255] # 标记为红色

    cv2.imshow('Harris Corners', img_dst)
    print("按任意键关闭Harris角点窗口...")
    cv2.waitKey(0)
    cv2.destroyAllWindows()

4.2 SIFT 和 SURF 特征

  • SIFT (Scale-Invariant Feature Transform):对尺度、旋转、光照变化及部分视角变化具有不变性的特征。
  • SURF (Speeded Up Robust Features):SIFT的加速版本,使用盒式滤波器近似SIFT的计算,速度更快。

注意:SIFT和SURF可能涉及专利,商业使用时需注意。

4.3 ORB 特征描述符

ORB (Oriented FAST and Rotated BRIEF) 是一种免费且高效的特征检测与描述算法。它结合了FAST角点检测和BRIEF描述符,并加入了方向信息,使其对旋转具有鲁棒性,且速度快。

import cv2
import numpy as np

# 假设 img 已经成功读取
if img is not None:
    gray_img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

    # --- ORB 特征检测与描述 ---
    orb = cv2.ORB_create() # 创建ORB检测器

    # detectAndCompute同时检测关键点和计算描述符
    keypoints, descriptors = orb.detectAndCompute(gray_img, None)

    # 在原图上绘制关键点
    # flags=cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS 会绘制关键点的方向和大小
    img_with_keypoints = cv2.drawKeypoints(img, keypoints, None, color=(0, 255, 0),
                                           flags=cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS)

    cv2.imshow('ORB Keypoints', img_with_keypoints)
    print(f"检测到 {len(keypoints)} 个ORB关键点。")
    print("按任意键关闭ORB关键点窗口...")
    cv2.waitKey(0)
    cv2.destroyAllWindows()

5. 目标检测与跟踪

5.1 Haar 级联检测

Haar级联分类器(常用于人脸检测)是基于Adaboost学习的快速检测器,利用Haar-like特征和积分图实现高效识别。OpenCV提供了预训练的.xml文件。

import cv2

# 加载预训练的Haar级联分类器 XML文件
# 请确保 'haarcascade_frontalface_default.xml' 文件在正确路径
face_cascade = cv2.CascadeClassifier('haarcascade_frontalface_default.xml')

if face_cascade.empty():
    print("错误:无法加载Haar级联分类器文件。请检查文件路径。")
else:
    # 假设 img 已经成功读取
    if img is not None:
        gray_img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

        # --- 人脸检测 ---
        # detectMultiScale 参数:
        # image: 输入灰度图
        # scaleFactor: 每次图像尺寸缩小的比例,如1.1表示缩小10%。值越小,检测越精细但慢。
        # minNeighbors: 每个候选矩形保留的邻居数。值越大,检测越精确但漏检可能增加。
        # minSize: 最小可能的目标尺寸。
        faces = face_cascade.detectMultiScale(gray_img, scaleFactor=1.1, minNeighbors=5, minSize=(30, 30))

        # 在原图上绘制检测到的人脸矩形框
        for (x, y, w, h) in faces:
            cv2.rectangle(img, (x, y), (x+w, y+h), (255, 0, 0), 2) # 蓝色矩形框

        cv2.imshow('Detected Faces', img)
        print(f"检测到 {len(faces)} 张人脸。")
        print("按任意键关闭人脸检测窗口...")
        cv2.waitKey(0)
        cv2.destroyAllWindows()

5.2 目标检测算法(YOLO、Faster R-CNN 等)

深度学习目标检测算法(如YOLO, Faster R-CNN, SSD)极大地提升了检测精度和速度。

  • YOLO:单阶段,速度快,适合实时。
  • Faster R-CNN:两阶段,精度高,速度相对慢。
  • SSD:单阶段,兼顾速度与多尺度检测。

OpenCV的 cv2.dnn 模块可加载并运行这些模型。

5.3 目标跟踪算法(KLT、Meanshift 等)

目标跟踪在视频序列中持续追踪目标,而非每帧独立检测。

  • KLT (Kanade-Lucas-Tomasi):基于光流法,跟踪特征点。
  • Meanshift / Camshift:基于颜色直方图,适合跟踪颜色显著的目标。

OpenCV提供了多种跟踪器(如 cv2.TrackerKCF_create(), cv2.TrackerCSRT_create())。


6. 摄像头与视频处理

6.1 摄像头捕获和设置

通过OpenCV可以轻松捕获摄像头视频流。

import cv2

# --- 摄像头捕获 ---
# 0 通常是默认摄像头,如果有多个请尝试 1, 2...
cap = cv2.VideoCapture(0)

if not cap.isOpened():
    print("错误:无法打开摄像头。")
else:
    print("摄像头已打开。按 'q' 键退出。")
    while True:
        ret, frame = cap.read() # ret: 是否成功读取, frame: 捕获的帧

        if not ret:
            print("无法接收帧 (视频流结束?)。退出...")
            break

        # --- 实时视频处理 (示例: 灰度化) ---
        gray_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)

        # 显示原始帧和处理后的帧
        cv2.imshow('Original Camera Feed', frame)
        cv2.imshow('Grayscale Feed', gray_frame)

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

    # 释放摄像头资源并关闭窗口
    cap.release()
    cv2.destroyAllWindows()

6.2 视频文件读写

处理视频文件与摄像头流类似,只需将摄像头索引替换为文件路径。

import cv2

# --- 视频文件读取 ---
video_path = 'input_video.mp4' # 替换为你的视频文件路径
cap = cv2.VideoCapture(video_path)

if not cap.isOpened():
    print(f"错误:无法打开视频文件 {video_path}。")
else:
    # 获取视频属性
    frame_count = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
    fps = cap.get(cv2.CAP_PROP_FPS)
    frame_width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
    frame_height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))

    print(f"视频信息: 帧数={frame_count}, FPS={fps}, 分辨率={frame_width}x{frame_height}")

    # --- 视频文件写入 ---
    # 定义编码器 (FourCC code)
    # 'XVID': .avi格式常用
    # 'mp4v': MP4格式常用
    fourcc = cv2.VideoWriter_fourcc(*'XVID')
    # 创建 VideoWriter 对象
    # 参数: 输出文件名, FourCC编码器, FPS, 帧尺寸 (宽, 高)
    out = cv2.VideoWriter('output_video.avi', fourcc, fps, (frame_width, frame_height), isColor=True)

    frame_idx = 0
    while cap.isOpened():
        ret, frame = cap.read()
        if not ret:
            break # 视频结束或读取失败

        frame_idx += 1
        # --- 实时视频处理 (示例: 边缘检测) ---
        gray_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
        edges_frame = cv2.Canny(gray_frame, 100, 200)
        # Canny 输出是灰度图,若保存彩色视频,需转换回三通道
        edges_frame_color = cv2.cvtColor(edges_frame, cv2.COLOR_GRAY2BGR)

        # 将处理后的帧写入输出视频
        out.write(edges_frame_color)

        # 可选: 显示处理过程
        cv2.imshow('Video Processing', edges_frame_color)

        if cv2.waitKey(1) & 0xFF == ord('q'):
            break

    # 释放资源
    cap.release()
    out.release()
    cv2.destroyAllWindows()
    print(f"视频处理完成,已保存至 output_video.avi (共处理 {frame_idx} 帧)。")

6.3 实时视频处理

实时视频处理是将摄像头或视频文件读取的每一帧进行即时分析和处理。核心在于在一个循环中完成:读取帧 -> 应用算法 -> 显示/保存结果 -> 检查退出条件。


7. 深度学习与 OpenCV

OpenCV的 dnn (Deep Neural Network) 模块极大地简化了在C++和Python中集成和运行深度学习模型的过程,无需复杂配置即可支持多种框架。

7.1 使用 DNN 模块集成深度学习模型

cv2.dnn 支持加载多种框架的模型,包括:Caffe, TensorFlow, Keras, PyTorch, Darknet (YOLO), ONNX。

核心步骤:

  1. 加载模型cv2.dnn.readNet()cv2.dnn.readNetFrom<framework>()
  2. 数据预处理:使用 cv2.dnn.blobFromImage() 将输入图像转换为模型期望的格式(调整尺寸、归一化等)。
  3. 前向传播net.setInput(blob) 后执行 outputs = net.forward()
  4. 后处理:解析模型输出,转化为易读信息(如类别、置信度、边界框)。

7.2 在 OpenCV 中使用预训练的深度学习模型

以使用 MobileNet 进行图像分类为例(需下载模型文件):

import cv2
import numpy as np

# --- 请替换为您的模型和标签文件实际路径 ---
# 例如:
# prototxt_path = 'models/mobilenet_deploy.prototxt' # Caffe模型配置文件
# model_path = 'models/mobilenet_iter_73000.caffemodel' # Caffe模型权重文件
# class_labels_path = 'models/synset_words.txt'        # ImageNet类别标签

# 示例文本路径,您需要提供真实的文件
prototxt_path = 'path/to/your/mobilenet.prototxt'
model_path = 'path/to/your/mobilenet.caffemodel'
class_labels_path = 'path/to/your/synset_words.txt'

# --- 加载类别标签 ---
try:
    with open(class_labels_path, 'r') as f:
        classes = [line.strip() for line in f.readlines()]
except FileNotFoundError:
    print(f"错误:类别标签文件未找到于 {class_labels_path}")
    classes = []

# --- 加载深度学习模型 ---
try:
    # 示例:加载Caffe模型,请根据您的模型类型选择相应函数
    net = cv2.dnn.readNetFromCaffe(prototxt_path, model_path)
    print("模型加载成功!")
except cv2.error as e:
    print(f"模型加载失败: {e}")
    print("请检查模型文件和 prototxt 文件路径是否正确,以及是否为 OpenCV DNN 支持的格式。")
    exit()
except FileNotFoundError:
    print(f"错误:模型文件或配置文件未找到。请检查路径。")
    exit()


# --- 图像读取与预处理 ---
# 假设 img 已经成功读取
if img is not None:
    # 预处理图像:调整尺寸到模型期望的 (224x224),进行均值减法、通道交换(RGB vs BGR)和缩放
    blob = cv2.dnn.blobFromImage(img, scalefactor=1.0/255, size=(224, 224), mean=(0,0,0), swapRB=True, crop=False)

    # --- 前向传播 ---
    net.setInput(blob)
    outputs = net.forward() # 获取模型的预测输出

    # --- 后处理 ---
    if outputs.size > 0:
        # outputs 的形状通常是 (1, num_classes)
        predicted_class_index = np.argmax(outputs)
        confidence = outputs[0, predicted_class_index]

        # 获取类别标签
        if classes and predicted_class_index < len(classes):
            predicted_class_label = classes[predicted_class_index]
            # 标签可能包含序号,如 "n02106655 cat, ..."
            if ':' in predicted_class_label:
                predicted_class_label = predicted_class_label.split(':', 1)[1].strip()
        else:
            predicted_class_label = f"Class_{predicted_class_index}"

        result_text = f"Prediction: {predicted_class_label}, Confidence: {confidence:.2f}"
        print(result_text)

        # 在图像上显示结果
        cv2.putText(img, result_text, (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.9, (0, 255, 0), 2)
        cv2.imshow("Image Classification", img)
        print("按任意键关闭图像...")
        cv2.waitKey(0)
        cv2.destroyAllWindows()
    else:
        print("模型未产生有效输出。")

重要提示:您需要根据您下载的深度学习模型(如MobileNet, YOLO等)的实际文件路径和格式,修改 prototxt_path, model_path, class_labels_path,并可能需要调整 cv2.dnn.readNetFrom... 函数。


8. 项目实战

8.1 实际应用案例

  • 人脸识别

    • 技术栈:Haar级联/深度学习检测器 + 特征提取(如FaceNet)+ 比对。
    • 流程:检测人脸 -> 提取特征向量 -> 与数据库比对。
  • 车牌识别 (LPR)

    • 技术栈:目标检测(车牌定位)+ 图像处理(字符分割)+ OCR(字符识别)。
    • 流程:定位车牌 -> 预处理 -> 分割字符 -> 识别字符 -> 组合成车牌号。
  • 手势识别

    • 技术栈:手部检测 + 关键点提取/形状分析 + 分类器(SVM, CNN)。
    • 流程:检测手部 -> 提取手部特征 -> 分类识别手势。

8.2 项目开发流程和实施步骤

典型的CV项目开发流程:

  1. 需求分析:明确问题、输入、输出及性能指标。
  2. 数据收集与标注:准备高质量数据集。
  3. 算法选择与模型设计:选择合适算法或网络架构。
  4. 环境搭建与实现:配置开发环境,编写代码。
  5. 模型训练与调优:使用数据训练模型,优化参数。
  6. 测试与评估:用测试集评估模型性能。
  7. 部署与集成:将模型部署到目标平台。
  8. 维护与迭代:持续监控,根据反馈优化。

9. 性能优化与部署

9.1 OpenCV 性能优化技巧

  • 选用高效算法:如ORB替代SIFT/SURF(若无专利顾虑),或轻量级深度学习模型。
  • 利用OpenCV优化特性:确保编译时启用了SIMD指令集(SSE, AVX),利用多线程并行计算。
  • 合理选择数据类型和尺寸:优先使用CV_8U,调整图像尺寸至需求。
  • 避免不必要计算:按需进行颜色空间转换,只在必要时执行复杂检测。
  • GPU加速:使用OpenCV的CUDA模块或dnn模块的GPU推理功能。

9.2 在嵌入式系统上部署 OpenCV 应用

嵌入式部署需考虑资源限制:

  • 交叉编译:在开发主机上为目标设备编译OpenCV库和应用。
  • 配置CMake:使用目标设备的工具链文件,禁用不必要模块。
  • 部署:将编译好的库文件(.so)和应用部署到设备。
  • 优化:根据嵌入式设备性能进行算法选择和参数调整。

10. 未来趋势与发展

10.1 计算机视觉领域的最新趋势

  • Transformer模型:在视觉领域的广泛应用(ViT等)。
  • 生成式AI:GANs, Diffusion Models用于图像生成与编辑。
  • 3D计算机视觉:3D重建、场景理解。
  • 可解释AI (XAI):理解模型决策过程。
  • 自监督/弱监督学习:减少对标注数据的依赖。
  • 统一模型:同时处理多种CV任务。

10.2 OpenCV 的未来发展方向

  • 增强DNN模块:支持更多模型格式,优化GPU/NPU加速。
  • 集成前沿算法:持续引入Transformer等最新研究成果。
  • 性能与效率提升:优化核心算法,降低计算开销。
  • 跨平台与多语言支持:保持广泛的可用性。
  • 生态系统整合:加强与其他AI库和框架的协同。

Logo

火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。

更多推荐