本教程针对果云科技下的产品 ESP32-S3 CAM 开发板 + OV3660 同时想要使用micropython的,在这里注意了,如果各位还没有去买,请去买乐鑫旗下的产品,乐鑫已经公开了ESP32-S3 CAM 系列的例程库,很方便使用,至于博主为啥不买,呜呜呜~这里的痛苦不多做赘述,相信跟博主处于相同境遇下的小伙伴能够感同身受。为此,博主在实践过后总结了以下使用教程,主要还是借鉴了乐鑫官方的例程库进行参考试出来的,如果有小伙伴们有其他方案,欢迎在博主评论下留言给出更好的方案。

一、使用前必读

  • OV3660 与 OV2640 不通用默认例程,直接刷网上例程 100% 失败
  • 必须使用 支持 ESP32-S3 CAM 的 MicroPython 固件(果云给的固件,这里注意,必须要用烧录软件进行固件的烧录,不要使用Thonny内置的烧录,否则会出现异常)
  • 必须开启 PSRAM(8MB/16MB),否则拍照会直接崩溃
  • 本代码已自动处理:重复初始化、JPEG 验证、结束符截断、大小限制
  • 关于后续,如果后续大家有其他需要,可以在评论中留言,我看到会总结一下再次把相关问题进行解决并发布的~

二、硬件信息

  • 主控:ESP32-S3
  • 摄像头:OV3660
  • 接口:DVP 并行接口
  • 最大分辨率:UXGA (2048×1536)
  • 本工程推荐使用:QVGA(320×240)VGA(640×480) 最稳定,其余的像SVGA,XGA均无法使用,各位也可以尝试一下其他分辨率看能否使用。
  • 针脚图

三、软件环境

  • 编程工具:Thonny(https://thonny.org/)
  • 固件:ESP32-S3 MicroPython(带 camera 驱动 + PSRAM 使能)
  • 库依赖:cameraubinasciitime(均为内置库,无需安装)

四、函数 API 文档

1. camera.init(channel, format=..., framesize=...)

功能:初始化摄像头硬件(DVP、时钟、传感器、DMA、PSRAM)。

camera.init(channel, format=camera.JPEG, framesize=camera.FRAME_QVGA)

参数:

  • channel:int 固定填 0(ESP32‑S3 CAM 只有 1 路摄像头)

  • format:int 图像输出格式

    • camera.JPEG(OV3660 唯一稳定可用)
    • camera.GRAYSCALE
    • camera.RGB565
    • camera.YUV422
  • framesize:int 输出分辨率(OV3660 全部支持)

返回值: 无,失败直接抛出异常

异常: 初始化失败、传感器无响应、PSRAM 错误

2.camera.capture()

功能:捕获一帧图像,返回原始字节流(JPEG / RGB / GRAY)

frame = camera.capture()

返回值:

  • 成功:bytes 图像数据
  • 失败:None

说明:

  • OV3660 输出为标准 JPEG 数据
  • 包含 0xFFD8 开头、0xFFD9 结尾

3.camera.deinit()

功能:关闭摄像头、释放时钟、关闭 DVP、释放 DMA 内存

camera.deinit()

说明:

  • 重新初始化前必须调用
  • 防止重复初始化导致死机

4. 常量(分辨率 framesize)

全部 OV3660 实测可用:

camera.FRAME_QQVGA 160x120
camera.FRAME_QVGA 320x240
camera.FRAME_VGA 640x480
camera.FRAME_QXGA 2048x1536

五. OV3660 重要注意事项

  • 必须启用 PSRAM,否则无法捕获图像
  • 只支持 JPEG 输出稳定
  • 初始化前必须先 deinit()
  • 分辨率越高,速度越慢、内存占用越大
  • 图像数据必须检查 FF D8 开头和 FF D9 结尾
  • MicroPython 固件必须是 ESP32‑S3 CAM 专用版

六. 使用教程

1.工具函数

这里为了方便大家使用,我将核心使用封装为一个utils工具函数文件,使用这个文件足够大家在很多情况下进行摄像头的使用了。

# camera_utils.py
# ESP32-S3 CAM + OV3660 MicroPython 稳定驱动
# 功能:摄像头初始化、拍照、JPEG验证、自动裁剪结束符、大小限制、Base64编码

import camera
import ubinascii
import time

def init_camera(camera_format, frame_size):
    """
    摄像头初始化(自动防卡死、重复初始化)
    :param camera_format: 图像格式 → camera.JPEG 最稳定
    :param frame_size: 分辨率 → 推荐 QVGA(320x240) / VGA(640x480)
    :return: True 成功 / False 失败
    """
    # 尝试关闭之前的摄像头(防止占用)
    try:
        camera.deinit()
    except:
        pass
    time.sleep(1)
    
    try:
        # OV3660 核心初始化
        camera.init(0, format=camera_format, framesize=frame_size)
        print("摄像头初始化成功")
        return True
    except Exception as e:
        print(f"摄像头初始化失败: {e}")
        return False

def capture_and_encode_image():
    """
    拍照 → 验证JPEG → 自动截尾 → 大小限制 → Base64编码
    :return: base64 图像字符串 / None
    """
    try:
        # 1. 捕获一帧图像
        frame = camera.capture()
        if not frame:
            print("摄像头捕获失败")
            return None
        
        print(f"捕获图像: {len(frame)} 字节")
        
        # 2. 验证 JPEG 数据是否有效
        is_valid, msg = validate_jpeg_data(frame)
        if not is_valid:
            print(f"JPEG验证失败: {msg}")
            return None
        
        # 3. 寻找 JPEG 结束符 0xFFD9 并截断(解决数据尾脏数据)
        jpeg_end_pos = -1
        for i in range(len(frame) - 1):
            if frame[i] == 0xFF and frame[i+1] == 0xD9:
                jpeg_end_pos = i + 2
                break
        
        if jpeg_end_pos == -1:
            print("找不到JPEG结束标记")
            return None
        
        # 截取有效图像
        valid_frame = frame[:jpeg_end_pos]
        
        # 4. 图像大小限制(可根据需求修改)
        max_size = 3000  # 3KB 限制
        if len(valid_frame) > max_size:
            print(f"图像太大 ({len(valid_frame)} > {max_size} 字节),丢弃")
            return None
        
        # 5. 转 Base64
        img_b64 = ubinascii.b2a_base64(valid_frame).decode().strip()
        img_b64 = img_b64.replace('\n', '').replace('\r', '')
        
        print(f"编码完成: {len(img_b64)} 字符")
        return img_b64
    
    except Exception as e:
        print(f"捕获/编码错误: {e}")
        return None

def validate_jpeg_data(jpeg_data):
    """
    JPEG 完整性验证
    检查:开头 0xFFD8 + 结尾 0xFFD9
    """
    try:
        if len(jpeg_data) < 4:
            return False, "数据太短"
        
        # JPEG 必须以 0xFFD8 开头
        if jpeg_data[0] != 0xFF or jpeg_data[1] != 0xD8:
            return False, "不是有效的JPEG文件头"
        
        # 必须包含结束符 0xFFD9
        has_end_marker = False
        for i in range(len(jpeg_data) - 1):
            if jpeg_data[i] == 0xFF and jpeg_data[i+1] == 0xD9:
                has_end_marker = True
                break
        
        if not has_end_marker:
            return False, "JPEG缺少结束标记"
        
        return True, "JPEG数据有效"
    
    except Exception as e:
        return False, f"验证异常: {e}"

2.使用示例

# main.py
import camera
from camera_utils import init_camera, capture_and_encode_image

# ==================== 配置区 ====================
# 图像格式:JPEG 最稳定(OV3660 推荐)
CAMERA_FORMAT = camera.JPEG

# 分辨率(选一个)
# frame_size = camera.QVGA  # 320x240 最稳定
frame_size = camera.VGA    # 640x480 稳定
# frame_size = camera.SVGA  # 800x600
# ================================================

print("=== ESP32-S3 CAM OV3660 启动 ===")

# 初始化摄像头
if init_camera(CAMERA_FORMAT, frame_size):
    # 拍照并获取 Base64
    img_base64 = capture_and_encode_image()
    
    if img_base64:
        print("\n 拍照成功!Base64 前50字符:")
        print(img_base64[:50], "...")
    else:
        print("\n 拍照失败")
else:
    print("\n 摄像头无法启动")

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐