强曰为道

与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

第 16 章 — Docker 部署

第 16 章 — Docker 部署

16.1 为什么使用 Docker

问题Docker 解决方案
“在我机器上能跑”完全一致的运行环境
依赖冲突容器隔离
部署复杂一条命令启动
扩展困难容器编排
环境配置耗时预构建镜像

16.2 基础 Dockerfile

16.2.1 最小 OpenCV 镜像

# Dockerfile.opencv
FROM python:3.11-slim

# 安装系统依赖(OpenCV 需要)
RUN apt-get update && apt-get install -y --no-install-recommends \
    libgl1-mesa-glx \
    libglib2.0-0 \
    libsm6 \
    libxrender1 \
    libxext6 \
    && rm -rf /var/lib/apt/lists/*

# 安装 OpenCV
RUN pip install --no-cache-dir opencv-python-headless numpy

WORKDIR /app
COPY . .

CMD ["python", "main.py"]

16.2.2 生产级镜像

# Dockerfile.production
FROM python:3.11-slim AS base

# 安装系统依赖
RUN apt-get update && apt-get install -y --no-install-recommends \
    libgl1-mesa-glx \
    libglib2.0-0 \
    libsm6 \
    libxrender1 \
    libxext6 \
    libgstreamer1.0-0 \
    libavcodec-dev \
    libavformat-dev \
    libswscale-dev \
    && rm -rf /var/lib/apt/lists/*

WORKDIR /app

# 先安装依赖(利用 Docker 缓存)
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# 复制应用代码
COPY . .

# 健康检查
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
    CMD python -c "import cv2; assert cv2.__version__" || exit 1

# 非 root 用户
RUN useradd -m appuser
USER appuser

EXPOSE 8000
CMD ["python", "main.py"]

16.3 GPU Docker 镜像

16.3.1 CUDA 基础镜像

# Dockerfile.gpu
FROM nvidia/cuda:12.2.0-runtime-ubuntu22.04

# 安装 Python
RUN apt-get update && apt-get install -y \
    python3 python3-pip \
    libgl1-mesa-glx libglib2.0-0 \
    && rm -rf /var/lib/apt/lists/*

# 安装 OpenCV(含 CUDA)
RUN pip install --no-cache-dir \
    opencv-contrib-python-headless \
    numpy

# 验证 CUDA 支持
RUN python3 -c "import cv2; print(cv2.getBuildInformation())"

WORKDIR /app
COPY . .
CMD ["python3", "main.py"]

16.3.2 自定义编译 OpenCV + CUDA

# Dockerfile.opencv-cuda
FROM nvidia/cuda:12.2.0-devel-ubuntu22.04 AS builder

RUN apt-get update && apt-get install -y \
    build-essential cmake git python3-dev python3-numpy \
    libjpeg-dev libpng-dev libtiff-dev \
    libavcodec-dev libavformat-dev libswscale-dev \
    libgtk-3-dev libtbb-dev libeigen3-dev \
    && rm -rf /var/lib/apt/lists/*

# 下载 OpenCV 源码
RUN git clone --depth 1 --branch 4.10.0 \
    https://github.com/opencv/opencv.git /opt/opencv && \
    git clone --depth 1 --branch 4.10.0 \
    https://github.com/opencv/opencv_contrib.git /opt/opencv_contrib

# 编译
RUN mkdir /opt/opencv/build && cd /opt/opencv/build && cmake \
    -D CMAKE_BUILD_TYPE=Release \
    -D CMAKE_INSTALL_PREFIX=/usr/local \
    -D OPENCV_EXTRA_MODULES_PATH=/opt/opencv_contrib/modules \
    -D WITH_CUDA=ON \
    -D CUDA_ARCH_BIN="7.5,8.0,8.6,8.9" \
    -D WITH_CUDNN=ON \
    -D OPENCV_DNN_CUDA=ON \
    -D BUILD_EXAMPLES=OFF \
    -D BUILD_TESTS=OFF \
    -D PYTHON3_EXECUTABLE=/usr/bin/python3 \
    .. && make -j$(nproc) && make install && ldconfig

# 最终镜像(只包含运行时)
FROM nvidia/cuda:12.2.0-runtime-ubuntu22.04

RUN apt-get update && apt-get install -y \
    python3 python3-numpy libgl1-mesa-glx libglib2.0-0 \
    && rm -rf /var/lib/apt/lists/*

COPY --from=builder /usr/local /usr/local
RUN ldconfig

WORKDIR /app
COPY . .
CMD ["python3", "main.py"]

16.3.3 运行 GPU 容器

# 运行 GPU 容器
docker run --gpus all -it --rm \
    -v $(pwd):/app \
    opencv-gpu:latest \
    python3 main.py

# 指定 GPU
docker run --gpus '"device=0"' -it --rm opencv-gpu:latest

16.4 无头(Headless)渲染

服务器环境通常没有显示器,需要特殊处理 imshow

16.4.1 使用 headless 包

# 使用 headless 版本(无 GUI 依赖)
RUN pip install opencv-python-headless

16.4.2 保存为文件替代显示

# 替代 imshow
# cv2.imshow("result", img)  # 会报错

# 方法 1: 保存为文件
cv2.imwrite("/app/output/result.jpg", img)

# 方法 2: 生成 base64 用于 Web
import base64

def image_to_base64(image, fmt=".jpg"):
    _, buffer = cv2.imencode(fmt, image)
    return base64.b64encode(buffer).decode("utf-8")

# 方法 3: Matplotlib 保存
import matplotlib.pyplot as plt
plt.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
plt.savefig("output.png", dpi=150, bbox_inches="tight")

16.5 批量处理

"""
batch_processor.py — Docker 批量图像处理
"""
import cv2
import os
import glob
from pathlib import Path

def batch_process(input_dir, output_dir, process_func):
    os.makedirs(output_dir, exist_ok=True)
    files = glob.glob(os.path.join(input_dir, "*.*"))
    supported = {".jpg", ".jpeg", ".png", ".bmp", ".tiff"}

    total = 0
    for filepath in files:
        ext = Path(filepath).suffix.lower()
        if ext not in supported:
            continue

        img = cv2.imread(filepath)
        if img is None:
            continue

        result = process_func(img)
        output_path = os.path.join(output_dir, Path(filepath).name)
        cv2.imwrite(output_path, result)
        total += 1

    print(f"处理完成: {total} 个文件")

# 使用
# batch_process("/app/input", "/app/output", my_process_func)

16.6 微服务部署

FastAPI 图像处理服务

"""
api_server.py — OpenCV 微服务
"""
from fastapi import FastAPI, UploadFile, File
from fastapi.responses import StreamingResponse
import cv2
import numpy as np
import io

app = FastAPI(title="OpenCV 图像处理服务")

@app.post("/detect_edges")
async def detect_edges(file: UploadFile = File(...)):
    # 读取上传文件
    contents = await file.read()
    nparr = np.frombuffer(contents, np.uint8)
    img = cv2.imdecode(nparr, cv2.IMREAD_COLOR)

    # 处理
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    edges = cv2.Canny(gray, 50, 150)

    # 编码返回
    _, buffer = cv2.imencode(".jpg", edges)
    return StreamingResponse(
        io.BytesIO(buffer.tobytes()),
        media_type="image/jpeg"
    )

@app.post("/detect_objects")
async def detect_objects(file: UploadFile = File(...)):
    contents = await file.read()
    nparr = np.frombuffer(contents, np.uint8)
    img = cv2.imdecode(nparr, cv2.IMREAD_COLOR)

    # YOLO 检测(示例)
    # detections = yolo_detector.detect(img)

    return {"status": "ok", "objects": []}

# docker run -p 8000:8000 opencv-api
# Dockerfile.api
FROM python:3.11-slim

RUN apt-get update && apt-get install -y \
    libgl1-mesa-glx libglib2.0-0 \
    && rm -rf /var/lib/apt/lists/*

RUN pip install --no-cache-dir \
    opencv-python-headless \
    numpy \
    fastapi \
    uvicorn

WORKDIR /app
COPY . .

EXPOSE 8000
CMD ["uvicorn", "api_server:app", "--host", "0.0.0.0", "--port", "8000"]

16.7 Docker Compose

# docker-compose.yml
version: "3.8"

services:
  opencv-api:
    build:
      context: .
      dockerfile: Dockerfile.api
    ports:
      - "8000:8000"
    volumes:
      - ./data:/app/data
    environment:
      - MODEL_PATH=/app/models/yolov8n.onnx
      - CONF_THRESHOLD=0.5
    deploy:
      resources:
        reservations:
          devices:
            - driver: nvidia
              count: 1
              capabilities: [gpu]
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
      interval: 30s
      timeout: 10s
      retries: 3

  worker:
    build:
      context: .
      dockerfile: Dockerfile.gpu
    volumes:
      - ./data/input:/app/input
      - ./data/output:/app/output
    deploy:
      resources:
        reservations:
          devices:
            - driver: nvidia
              count: 1
              capabilities: [gpu]

16.8 镜像优化

技巧效果说明
使用 slim 基础镜像减少 50%+python:3.11-slim
多阶段构建减少 70%+编译层不进最终镜像
合并 RUN 指令减少层数减少镜像大小
.dockerignore加速构建排除不需要的文件
pip –no-cache-dir减少 50MB+不缓存 pip 下载
headless 包减少依赖无 GUI 依赖
# .dockerignore
.git
__pycache__
*.pyc
*.pyo
public/
*.md
tests/
.env

16.9 常见 Docker 问题

问题解决方案
libGL.so.1 not found安装 libgl1-mesa-glx 或使用 headless
cannot connect to display使用 headless 版本
GPU 不可用--gpus all + nvidia-docker
容器内中文乱码安装字体 fonts-noto-cjk
图像颜色异常检查 BGR/RGB 转换
内存溢出限制输入图像大小

16.10 扩展阅读

资源链接说明
NVIDIA Container Toolkitdocs.nvidia.com/datacenter/cloud-nativeGPU 容器
Docker Best Practicesdocs.docker.com/developDocker 开发指南
下一章第 17 章 — 常见问题与调试内存/性能/编译

本章小结: 掌握了 OpenCV 的 Docker 容器化部署,包括基础镜像、GPU 镜像、无头渲染、批量处理和微服务架构。