OpenCV C/C++ 视频播放器 (支持调速和进度控制)

news2025/6/7 0:10:29

OpenCV C/C++ 视频播放器 (支持调速和进度控制)

本文将引导你使用 C++ 和 OpenCV 库创建一个功能稍复杂的视频播放器。该播放器不仅能播放视频,还允许用户通过滑动条来调整播放速度(加速/减速)以及控制视频的播放进度。
使用opencv打开不会压缩画质,你看起来的效果和其他播放器打开的不一样,会觉得很高清
请添加图片描述

目录

  1. 功能简介
  2. 先决条件
  3. 核心组件
  4. 代码实现
    • 全局变量与结构
    • 滑动条回调函数
    • 主函数 main
    • 完整代码
  5. 编译和运行
  6. 使用说明
  7. 代码解释
    • 速度控制
    • 进度控制
    • 暂停/播放
  8. 可能的改进
  9. 总结

功能简介

  • 播放本地视频文件。
  • 通过滑动条调整播放速度(例如,0.1x 到 4.0x)。
  • 通过滑动条跳转到视频的任意位置。
  • 显示当前播放进度。
  • 支持暂停和继续播放。

先决条件

  • C++ 编译器: 如 GCC (MinGW for Windows), Clang, 或 MSVC。
  • OpenCV 库: 版本 3.x 或 4.x 已安装并正确配置。
  • 基本的 C++ 知识: 函数、循环、变量、指针等。
  • 基本的 OpenCV 知识: cv::VideoCapture, cv::Mat, cv::imshow, cv::createTrackbar, cv::waitKey

核心组件

  1. cv::VideoCapture: 用于打开和读取视频帧。
  2. cv::imshow: 用于在窗口中显示视频帧。
  3. cv::createTrackbar: 用于创建速度控制和进度控制的滑动条。
  4. 回调函数: 用于响应滑动条数值的变化。
  5. 主循环: 控制视频的读取、显示、延迟和用户输入。

代码实现

我们将逐步构建代码。

全局变量与结构

为了在回调函数和主循环之间共享数据,我们会使用一些全局变量。对于更大型的应用,通常会封装在一个类中。

#include <opencv2/opencv.hpp>
#include <iostream>
#include <string>
#include <algorithm> // For std::max and std::min

// --- 全局变量 ---
cv::VideoCapture cap;
std::string window_name = "OpenCV Video Player";
int g_slider_progress = 0;          // 进度条的当前位置
int g_total_frames = 0;             // 视频总帧数
bool g_user_is_dragging_progress_slider = false; // 标记用户是否正在拖动进度条

// 速度控制相关
int g_speed_trackbar_val = 100;     // 速度条的值 (例如100代表1.0x)
const int g_speed_trackbar_max = 400; // 速度条最大值 (例如400代表4.0x)
const int g_speed_trackbar_min = 10;  // 速度条最小值 (例如10代表0.1x)
double g_current_fps_multiplier = 1.0; // 当前播放速度倍率

bool g_paused = false;              // 暂停状态
cv::Mat g_current_frame_for_pause;  // 用于暂停时显示的当前帧

滑动条回调函数

我们需要两个回调函数:一个用于进度条,一个用于速度条。

// --- 进度条回调函数 ---
void on_trackbar_progress(int pos, void* userdata) {
    if (g_user_is_dragging_progress_slider && cap.isOpened()) {
        // 只有当用户实际改变滑块位置时才跳转
        // (避免程序自身更新滑块位置时触发不必要的跳转)
        if (std::abs(pos - (int)cap.get(cv::CAP_PROP_POS_FRAMES)) > 1) { // 容差,避免微小抖动
             cap.set(cv::CAP_PROP_POS_FRAMES, pos);
        }
        g_slider_progress = pos; // 确保全局变量也更新
    }
}

// --- 速度条回调函数 ---
void on_trackbar_speed(int pos, void* userdata) {
    if (pos < g_speed_trackbar_min) { // 防止速度过低或为0
        g_speed_trackbar_val = g_speed_trackbar_min;
        cv::setTrackbarPos("Speed x0.01", window_name, g_speed_trackbar_min);
    } else {
        g_speed_trackbar_val = pos;
    }
    g_current_fps_multiplier = static_cast<double>(g_speed_trackbar_val) / 100.0;
}

// OpenCV的createTrackbar在内部处理鼠标事件,
// 我们需要一个通用的鼠标回调来检测用户是否开始/停止拖动进度条
void on_mouse_event_progress(int event, int x, int y, int flags, void* userdata) {
    // 这个函数可以用来更精确地控制 g_user_is_dragging_progress_slider
    // 但OpenCV的Trackbar没有直接提供这种拖动状态,这里简化处理
    // 简单地假设,如果回调被调用且值改变,就是用户操作
    // 更稳健的方法需要自己绘制滑动条或使用更高级的UI库

    // 这里我们依赖于一个简化的逻辑:在设置进度条位置前,先设置 g_user_is_dragging_progress_slider
    // 并在 on_trackbar_progress 中检查。
    // 对于进度条,更常见的是,当用户按下鼠标左键在滑动条上时,我们设置一个标志,
    // 释放时清除标志。OpenCV 的 createTrackbar 没有直接暴露这些事件。
    // 替代方案:on_trackbar_progress 被调用时,我们认为可能是用户操作。
    // 主循环中程序更新进度条时,我们不希望 on_trackbar_progress 错误地认为用户在拖动。
    // 这就是为什么我们在主循环中更新进度条时要小心。

    // 为了简化,我们直接在 on_trackbar_progress 中处理,并接受程序更新也可能调用它。
    // 如果 pos 与当前 cap.get(cv::CAP_PROP_POS_FRAMES) 不同,则认为是用户或程序触发的有效seek。
    // 在本例中,我们将在主循环中设置一个标志,表明是程序在更新。
}

注意: 精确检测用户是否正在 拖动 OpenCV 原生滑动条是比较棘手的。createTrackbar 的回调是在值 改变后 触发的。更理想的方案是,程序更新滑动条时不应触发 cap.set

一个更可行的简化方案是在 on_trackbar_progress 中,仅当值与视频当前帧显著不同时才 cap.set,并在主循环中更新 g_slider_progresscv::setTrackbarPos

// 简化的进度条回调
void on_trackbar_progress_simplified(int pos, void* userdata) {
    if (!cap.isOpened()) return;
    // 只有当滑动条的位置与视频当前帧位置有显著差异时,才认为是用户拖动并执行跳转
    // 或者我们引入一个外部标志来区分用户拖动和程序更新
    // 这里假设回调是用户操作的结果
    cap.set(cv::CAP_PROP_POS_FRAMES, pos);
    g_slider_progress = pos; // 同步全局变量
}

主函数 main

这里是所有逻辑的汇集处。

int main(int argc, char** argv) {
    std::string video_path;
    if (argc > 1) {
        video_path = argv[1];
    } else {
        std::cout << "请输入视频文件路径: ";
        std::cin >> video_path;
    }

    if (!cap.open(video_path)) {
        std::cerr << "错误: 无法打开视频文件: " << video_path << std::endl;
        return -1;
    }

    g_total_frames = static_cast<int>(cap.get(cv::CAP_PROP_FRAME_COUNT));
    double fps_original = cap.get(cv::CAP_PROP_FPS);

    if (fps_original <= 0) { // 防止除以0或负数
        std::cout << "警告: 无法获取视频的有效FPS,将使用默认值30 FPS。" << std::endl;
        fps_original = 30.0;
    }
    int base_delay_ms = static_cast<int>(1000.0 / fps_original);

    cv::namedWindow(window_name, cv::WINDOW_AUTOSIZE);

    // 创建进度条
    // 注意:传递 &g_slider_progress 使滑动条直接绑定到该变量
    // 当用户拖动时,g_slider_progress 会被OpenCV更新,然后回调被触发
    cv::createTrackbar("Progress", window_name, &g_slider_progress, g_total_frames > 0 ? g_total_frames - 1 : 0, on_trackbar_progress_simplified);

    // 创建速度条
    cv::createTrackbar("Speed x0.01", window_name, &g_speed_trackbar_val, g_speed_trackbar_max, on_trackbar_speed);
    cv::setTrackbarMin("Speed x0.01", window_name, g_speed_trackbar_min); // 设置最小值
    cv::setTrackbarPos("Speed x0.01", window_name, 100); // 初始速度1.0x (对应值100)
    on_trackbar_speed(100, 0); // 初始化速度倍率

    cv::Mat frame;
    int current_frame_number = 0;

    while (true) {
        if (!g_paused) {
            bool success = cap.read(frame);
            if (!success) { // 如果读取失败(视频结束或错误)
                std::cout << "视频结束或读取帧失败。" << std::endl;
                // 可以选择重置播放或退出
                cap.set(cv::CAP_PROP_POS_FRAMES, 0); // 重置到开头
                cv::setTrackbarPos("Progress", window_name, 0);
                g_slider_progress = 0;
                // continue; // 如果想循环播放
                g_paused = true; // 暂停在最后一帧或黑屏
                if(frame.empty() && !g_current_frame_for_pause.empty()){
                    frame = g_current_frame_for_pause.clone(); // 显示暂停前的最后一帧
                } else if (frame.empty()){
                     break; // 如果一开始就没帧,则退出
                }
            }
            if (!frame.empty()) {
                 g_current_frame_for_pause = frame.clone(); // 保存当前帧用于暂停
            }
        } else { // 如果是暂停状态
            if (g_current_frame_for_pause.empty() && cap.isOpened()) { 
                // 如果暂停时没有缓存帧,尝试读取一帧
                cap.set(cv::CAP_PROP_POS_FRAMES, g_slider_progress); //确保位置正确
                cap.read(g_current_frame_for_pause);
            }
            // 如果仍然为空,可能视频有问题或已结束
            if(g_current_frame_for_pause.empty()){
                std::cout << "暂停时无有效帧可显示。" << std::endl;
                frame = cv::Mat::zeros(cv::Size(640,480), CV_8UC3); // 显示黑屏
            } else {
                 frame = g_current_frame_for_pause.clone(); // 使用暂停时缓存的帧
            }
        }
        
        if (frame.empty()){
            // 如果所有尝试后帧仍然为空,可能真的无法播放了
            std::cout << "错误:帧为空,无法继续播放。" << std::endl;
            break;
        }

        // 更新进度条的当前位置 (非用户拖动时)
        current_frame_number = static_cast<int>(cap.get(cv::CAP_PROP_POS_FRAMES));
        if (current_frame_number != g_slider_progress) { // 避免不必要的setTrackbarPos调用
            g_slider_progress = current_frame_number;
            cv::setTrackbarPos("Progress", window_name, g_slider_progress);
        }
        
        cv::imshow(window_name, frame);

        int delay = static_cast<int>(static_cast<double>(base_delay_ms) / g_current_fps_multiplier);
        if (delay <= 0) delay = 1; // waitKey至少需要1ms

        char key = (char)cv::waitKey(delay);

        if (key == 27 || key == 'q' || key == 'Q') { // ESC 或 q/Q 退出
            break;
        } else if (key == ' ') { // 空格键 暂停/播放
            g_paused = !g_paused;
            if (!g_paused && !g_current_frame_for_pause.empty()) {
                // 从暂停状态恢复时,确保视频捕获对象的位置与滑块一致
                // 防止因暂停期间滑块被拖动而导致的播放位置不匹配
                if(std::abs(g_slider_progress - (int)cap.get(cv::CAP_PROP_POS_FRAMES)) > 1) {
                    cap.set(cv::CAP_PROP_POS_FRAMES, g_slider_progress);
                }
            }
        }
    }

    cap.release();
    cv::destroyAllWindows();
    return 0;
}

完整代码

将上述所有部分(全局变量、回调函数、main 函数)合并到一个 .cpp 文件中。

#include <opencv2/opencv.hpp>
#include <iostream>
#include <string>
#include <algorithm> // For std::max and std::min

// --- 全局变量 ---
cv::VideoCapture cap;
std::string window_name = "OpenCV Video Player";
int g_slider_progress = 0;          // 进度条的当前位置
int g_total_frames = 0;             // 视频总帧数

// 速度控制相关
int g_speed_trackbar_val = 100;     // 速度条的值 (例如100代表1.0x)
const int g_speed_trackbar_max = 400; // 速度条最大值 (例如400代表4.0x)
const int g_speed_trackbar_min = 10;  // 速度条最小值 (例如10代表0.1x)
double g_current_fps_multiplier = 1.0; // 当前播放速度倍率

bool g_paused = false;              // 暂停状态
cv::Mat g_current_frame_for_pause;  // 用于暂停时显示的当前帧

// --- 进度条回调函数 ---
// 当用户拖动进度条时,此函数被调用
void on_trackbar_progress(int pos, void* userdata) {
    if (!cap.isOpened()) return;
    // `pos` 是滑动条的新位置
    // 我们只在 `pos` 与视频捕获对象的内部帧计数器显著不同时才设置帧位置,
    // 以避免在程序更新滑动条时(非用户拖动)产生循环调用或抖动。
    // `userdata` 在这里没有使用。
    if (std::abs(pos - static_cast<int>(cap.get(cv::CAP_PROP_POS_FRAMES))) > 1) {
         cap.set(cv::CAP_PROP_POS_FRAMES, pos);
    }
    g_slider_progress = pos; // 确保全局变量与滑动条同步
}

// --- 速度条回调函数 ---
// 当用户拖动速度条时,此函数被调用
void on_trackbar_speed(int pos, void* userdata) {
    if (pos < g_speed_trackbar_min) {
        g_speed_trackbar_val = g_speed_trackbar_min;
        // 如果用户尝试设置低于最小值,强制将滑动条也设回最小值
        cv::setTrackbarPos("Speed x0.01", window_name, g_speed_trackbar_min);
    } else {
        g_speed_trackbar_val = pos;
    }
    // 将滑动条的值 (例如 10 到 400) 转换为速度倍率 (例如 0.1x 到 4.0x)
    g_current_fps_multiplier = static_cast<double>(g_speed_trackbar_val) / 100.0;
}


int main(int argc, char** argv) {
    std::string video_path;
    if (argc > 1) {
        video_path = argv[1];
    } else {
        std::cout << "使用方法: " << argv[0] << " <视频文件路径>" << std::endl;
        std::cout << "请输入视频文件路径: ";
        std::getline(std::cin, video_path); // 使用getline读取可能带空格的路径
    }

    if (video_path.empty()){
        std::cerr << "错误: 未提供视频文件路径。" << std::endl;
        return -1;
    }
    
    // 移除路径两端可能存在的引号(常见于拖拽文件到命令行)
    if (video_path.front() == '"' && video_path.back() == '"') {
        video_path = video_path.substr(1, video_path.length() - 2);
    }


    if (!cap.open(video_path)) {
        std::cerr << "错误: 无法打开视频文件: \"" << video_path << "\"" << std::endl;
        return -1;
    }

    g_total_frames = static_cast<int>(cap.get(cv::CAP_PROP_FRAME_COUNT));
    double fps_original = cap.get(cv::CAP_PROP_FPS);

    if (fps_original <= 0) {
        std::cout << "警告: 无法获取视频的有效FPS,将使用默认值 30 FPS。" << std::endl;
        fps_original = 30.0;
    }
    int base_delay_ms = static_cast<int>(1000.0 / fps_original);

    cv::namedWindow(window_name, cv::WINDOW_AUTOSIZE);

    // 创建进度条
    if (g_total_frames > 0) {
        cv::createTrackbar("Progress", window_name, &g_slider_progress, g_total_frames - 1, on_trackbar_progress);
    }

    // 创建速度条
    cv::createTrackbar("Speed x0.01", window_name, &g_speed_trackbar_val, g_speed_trackbar_max, on_trackbar_speed);
    cv::setTrackbarMin("Speed x0.01", window_name, g_speed_trackbar_min);
    cv::setTrackbarPos("Speed x0.01", window_name, 100); // 初始速度1.0x
    on_trackbar_speed(100, 0); // 手动调用一次以初始化g_current_fps_multiplier

    cv::Mat frame;
    int current_frame_display_number = 0; // 用于UI显示的帧号

    std::cout << "按 '空格键' 暂停/播放, 'ESC' 或 'q' 退出." << std::endl;

    while (true) {
        if (!g_paused) {
            bool success = cap.read(frame);
            if (!success) {
                std::cout << "视频结束或读取帧失败。" << std::endl;
                // 视频结束时可以选择暂停在最后一帧
                g_paused = true; 
                if (g_current_frame_for_pause.empty()) { // 如果从未成功读取过帧
                    std::cout << "没有帧可以显示,退出。" << std::endl;
                    break;
                }
                frame = g_current_frame_for_pause.clone(); // 显示最后一帧
            } else {
                 g_current_frame_for_pause = frame.clone(); // 保存当前有效帧
            }
        } else { // 暂停状态
            if (g_current_frame_for_pause.empty()) {
                // 尝试在当前进度条位置读取一帧作为暂停帧
                // 这通常在视频开始就暂停,或跳转后暂停时发生
                if (cap.isOpened() && g_total_frames > 0) {
                    cap.set(cv::CAP_PROP_POS_FRAMES, g_slider_progress);
                    cap.read(g_current_frame_for_pause);
                }
            }
            // 如果仍然没有可显示的暂停帧,则显示黑屏或之前的帧
            if (!g_current_frame_for_pause.empty()) {
                frame = g_current_frame_for_pause.clone();
            } else {
                // 作为最后手段,显示一个黑屏
                cv::Size frame_size = cap.isOpened() ? 
                                      cv::Size(static_cast<int>(cap.get(cv::CAP_PROP_FRAME_WIDTH)), static_cast<int>(cap.get(cv::CAP_PROP_FRAME_HEIGHT))) :
                                      cv::Size(640, 480); // 默认大小
                if(frame_size.width <= 0 || frame_size.height <=0) frame_size = cv::Size(640,480);
                frame = cv::Mat::zeros(frame_size, CV_8UC3);
                 std::cout << "暂停时无有效帧可显示,显示黑屏。" << std::endl;
            }
        }
        
        if (frame.empty()){
            std::cout << "错误:帧为空,无法继续播放。" << std::endl;
            break;
        }

        // 更新进度条的当前位置 (仅当未被用户拖动时)
        // cv::VideoCapture::get(cv::CAP_PROP_POS_FRAMES) 获取的是下一帧的索引
        current_frame_display_number = static_cast<int>(cap.get(cv::CAP_PROP_POS_FRAMES));
        if (current_frame_display_number > 0 && !g_paused) { // 通常 POS_FRAMES 指向下一帧
            current_frame_display_number--; // 显示的是当前帧的编号
        }
        if (g_paused) { // 暂停时,进度条应显示当前暂停帧的编号
            current_frame_display_number = g_slider_progress;
        }


        // 避免在回调函数中再次设置,导致可能的抖动
        // 我们只在程序逻辑前进时更新滑动条
        // 而用户拖动滑动条时,回调函数会更新 g_slider_progress 和视频位置
        if (g_total_frames > 0 && std::abs(g_slider_progress - current_frame_display_number) > 0 && !g_paused) {
            // 只有当程序播放导致帧号变化时,才更新滑动条绑定的g_slider_progress
            // 并且确保不是因为回调函数刚刚设置了cap.set而立即又被这里的cap.get更新回来
            // 这是一个简化模型,最理想的是区分用户拖动和程序更新
             g_slider_progress = current_frame_display_number;
             cv::setTrackbarPos("Progress", window_name, g_slider_progress);
        } else if (g_total_frames > 0 && g_paused) {
            // 暂停时,如果用户拖动了滑块,g_slider_progress会被回调更新
            // 我们也需要确保滑块视觉位置正确
            cv::setTrackbarPos("Progress", window_name, g_slider_progress);
        }
        
        cv::imshow(window_name, frame);

        int delay = static_cast<int>(static_cast<double>(base_delay_ms) / g_current_fps_multiplier);
        if (delay <= 0) delay = 1;

        char key = (char)cv::waitKey(delay);

        if (key == 27 || key == 'q' || key == 'Q') {
            break;
        } else if (key == ' ') {
            g_paused = !g_paused;
            if (!g_paused) { // 从暂停恢复播放
                // 确保视频从滑动条指示的位置开始播放
                 if (cap.isOpened() && std::abs(g_slider_progress - static_cast<int>(cap.get(cv::CAP_PROP_POS_FRAMES))) > 1) {
                    cap.set(cv::CAP_PROP_POS_FRAMES, g_slider_progress);
                 }
                 std::cout << "播放中..." << std::endl;
            } else { // 进入暂停
                std::cout << "已暂停." << std::endl;
                // g_current_frame_for_pause 应该已经被保存了最新的帧
            }
        }
    }

    cap.release();
    cv::destroyAllWindows();
    return 0;
}

编译和运行

使用 CMake (推荐):

  1. 将上述代码保存为 video_player.cpp
  2. 创建 CMakeLists.txt 文件:
    cmake_minimum_required(VERSION 3.10)
    project(VideoPlayer)
    
    set(CMAKE_CXX_STANDARD 14) # C++14 或更高
    set(CMAKE_CXX_STANDARD_REQUIRED True)
    
    find_package(OpenCV REQUIRED)
    
    include_directories(${OpenCV_INCLUDE_DIRS})
    add_executable(VideoPlayer video_player.cpp)
    target_link_libraries(VideoPlayer PRIVATE ${OpenCV_LIBS})
    
  3. 编译:
    mkdir build
    cd build
    cmake ..
    make      # 或者ninja, 或者在Visual Studio中构建项目
    
  4. 运行:
    ./VideoPlayer /path/to/your/video.mp4
    # 或者在Windows上 (如果构建在Debug目录):
    # .\Debug\VideoPlayer.exe C:\path\to\your\video.mp4
    
    如果没有在命令行提供路径,程序会提示你输入。

使用 g++ (Linux/macOS):

g++ video_player.cpp -o VideoPlayer $(pkg-config --cflags --libs opencv4) # 或 opencv
./VideoPlayer /path/to/your/video.mp4

使用说明

  • 启动程序时,如果未在命令行参数中指定视频路径,程序会提示你输入。
  • Progress 滑动条: 显示当前播放进度,拖动它可以跳转到视频的不同位置。
  • Speed x0.01 滑动条: 调整播放速度。值为 100 表示 1.0x (正常速度),50 表示 0.5x (半速),200 表示 2.0x (两倍速)。范围是 0.1x 到 4.0x。
  • 空格键: 暂停或继续播放。
  • ESC 或 q/Q键: 退出播放器。

代码解释

速度控制

  • 视频的原始 fps_original (每秒帧数) 决定了每帧的基础延迟 base_delay_ms = 1000 / fps_original
  • 速度滑动条的值 g_speed_trackbar_val (例如范围 10-400) 被转换为一个倍率 g_current_fps_multiplier (例如 0.1 - 4.0)。
  • 实际的帧间延迟 delay 通过 base_delay_ms / g_current_fps_multiplier 计算。
    • 倍率 > 1.0 (加速): delay 减小,播放加快。
    • 倍率 < 1.0 (减速): delay 增大,播放减慢。
  • cv::waitKey(delay) 不仅提供延迟,还处理 GUI 事件(如滑动条的拖动)。

进度控制

  • 进度条的最大值设置为视频的总帧数 g_total_frames - 1
  • 当用户拖动进度条时,on_trackbar_progress 回调函数被触发。
  • 在该回调中,cap.set(cv::CAP_PROP_POS_FRAMES, pos) 用于将视频的当前读取位置跳转到滑动条指定的新位置 pos
  • 在主循环中,程序会读取视频的当前帧号 cap.get(cv::CAP_PROP_POS_FRAMES),并用 cv::setTrackbarPos 更新滑动条的显示位置,以反映实际的播放进度。

暂停/播放

  • 一个布尔变量 g_paused 跟踪播放状态。
  • 按下空格键时,g_paused 的状态会切换。
  • 如果 g_pausedtrue
    • 主循环不读取新的视频帧 (cap.read() 不被调用)。
    • cv::imshow 持续显示暂停时捕获的最后一帧 g_current_frame_for_pause
  • 如果 g_pausedfalse
    • 正常读取和显示视频帧。

可能的改进

  • 更精确的进度条拖动检测: 当前实现中,程序更新进度条也可能触发回调。可以通过更复杂的事件处理或自定义UI控件来区分用户拖动和程序更新。
  • 显示时间码: 在窗口上显示 “当前时间 / 总时间” 而不是帧号。
  • 音量控制和音频播放: OpenCV 主要处理视频。播放音频需要额外的库,如 SDL、PortAudio,或者使用 FFmpeg 更底层的 API。
  • 逐帧步进: 添加按钮或快捷键实现向前/向后逐帧播放。
  • 错误处理: 更详细的错误报告和处理。
  • UI美化: 使用 Qt 或其他 GUI 框架替换 OpenCV HighGUI 可以创建更美观和功能丰富的界面。
  • 播放列表: 支持加载和管理多个视频文件。

总结

这个项目展示了如何使用 OpenCV 创建一个具有基本播放控制功能的视频播放器。通过滑动条,用户可以方便地控制播放速度和进度,并通过键盘快捷键进行暂停和退出。这是一个很好的练习,可以帮助你熟悉 OpenCV 的视频处理和简单的 GUI 交互。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2400793.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Linux庖丁解牛】—自定义shell的编写!

1. 打印命令行提示符 在我们使用系统提供的shell时&#xff0c;每次都会打印出一行字符串&#xff0c;这其实就是命令行提示符&#xff0c;那我们自定义的shell当然也需要这一行字符串。 这一行字符串包含用户名&#xff0c;主机名&#xff0c;当前工作路径&#xff0c;所以&a…

Linux运维笔记:1010实验室电脑资源规范使用指南

文章目录 一. 检查资源使用情况&#xff0c;避免冲突1. 检查在线用户2. 检查 CPU 使用情况3. 检查 GPU 使用情况4. 协作建议 二. 备份重要文件和数据三. 定期清理硬盘空间四. 退出 ThinLinc 时注销&#xff0c;释放内存五. 校外使用时配置 VPN注意事项 总结 实验室的电脑配备了…

【Docker 从入门到实战全攻略(二):核心概念 + 命令详解 + 部署案例】

5. Docker Compose Docker Compose 是一个用于定义和运行多容器 Docker 应用的工具。通过一个 YAML 文件来配置应用服务&#xff0c;然后使用一个命令即可创建并启动所有服务。 基本命令 docker-compose up # 创建并启动所有服务 docker-compose down # 停止并移除容器、网络等…

【conda配置深度学习环境】

好的&#xff01;我们从头开始配置一个基于Conda的虚拟环境&#xff0c;覆盖深度学习&#xff08;如PyTorch&#xff09;和传统机器学习&#xff08;如XGBoost&#xff09;&#xff0c;并适配你的显卡&#xff08;假设为NVIDIA&#xff0c;若为AMD请告知&#xff09;。以下是完…

力扣4.寻找两个正序数组的中位数

文章目录 题目介绍题解 题目介绍 题解 题解链接&#xff1a;题解 核心思路&#xff1a;通过二分查找的确定分割点使左右两部分元素数量相等。 class Solution {public double findMedianSortedArrays(int[] nums1, int[] nums2) {int n1 nums1.length;int n2 nums2.length…

【相机基础知识与物体检测】更新中

参考&#xff1a; 黑马机器人 | 相机标定&物体检测https://robot.czxy.com/docs/camera/ 01-相机基础 相机基础概述 相机是机器视觉的基础&#xff0c;相机直接产生了相机数据。所有视觉算法都是作用在相机数据上的。相机数据的好坏&#xff0c;或者对相机数据的理解方式…

【前端】性能优化和分类

本页知识点参考&#xff1a;https://zhuanlan.zhihu.com/p/514222781 1. 加载性能优化 1.1 网站性能优化 content方法&#xff1a; 1&#xff09;减少HTTP请求&#xff1a;合并文件&#xff0c;CSS精灵&#xff0c;inline Image 2&#xff09;减少DNS查询&#xff1a;DNS缓存&…

PPO和GRPO算法

verl 是现在非常火的 rl 框架&#xff0c;而且已经支持了多个 rl 算法&#xff08;ppo、grpo 等等&#xff09;。 过去对 rl 的理解很粗浅&#xff08;只知道有好多个角色&#xff0c;有的更新权重&#xff0c;有的不更新&#xff09;&#xff0c;也曾硬着头皮看了一些论文和知…

rk3588 上运行smolvlm-realtime-webcam,将视频转为文字描述

smolvlm-realtime-webcam 是一个开源项目&#xff0c;结合了轻量级多模态模型 SmolVLM 和本地推理引擎 llama.cpp&#xff0c;能够在本地实时处理摄像头视频流&#xff0c;生成自然语言描述&#xff0c; 开源项目地址 https://github.com/ngxson/smolvlm-realtime-webcamhttps…

Rust 学习笔记:Box<T>

Rust 学习笔记&#xff1a;Box Rust 学习笔记&#xff1a;Box<T\>Box\<T> 简介使用 Box\<T\> 在堆上存储数据启用带有 box 的递归类型关于 cons 列表的介绍计算非递归类型的大小使用 Box\<T\> 获取大小已知的递归类型 Rust 学习笔记&#xff1a;Box<…

操作系统学习(十三)——Linux

一、Linux Linux 是一种类 Unix 的自由开源操作系统内核&#xff0c;由芬兰人 Linus Torvalds 于 1991 年首次发布。如今它广泛应用于服务器、桌面、嵌入式设备、移动设备&#xff08;如 Android&#xff09;等领域。 设计思想&#xff1a; 原则描述模块化与可移植性Linux 内…

NLP学习路线图(二十二): 循环神经网络(RNN)

在自然语言处理&#xff08;NLP&#xff09;的广阔天地中&#xff0c;序列数据是绝对的核心——无论是流淌的文本、连续的语音还是跳跃的时间序列&#xff0c;都蕴含着前后紧密关联的信息。传统神经网络如同面对一幅打散的拼图&#xff0c;无法理解词语间的顺序关系&#xff0c…

每日一C(1)C语言的内存分布

目录 代码区 常量区 全局/静态区 初始化数据段&#xff08;.data&#xff09; 未初始化数据段&#xff08;.bss&#xff09; 堆区 栈区 总结 今天我们学习的是C语言的内存分布&#xff0c;以及这些分区所存储的内容和其特点。今天的思维导图如下。 C语言作为一款直接处…

Photoshop使用钢笔绘制图形

1、绘制脸部路径 选择钢笔工具&#xff0c;再选择“路径”。 基于两个点绘制一个弯曲的曲线 使用Alt键移动单个点&#xff0c;该点决定了后续的曲线方向 继续绘制第3个点 最后一个点首尾是同一个点&#xff0c;使用钢笔保证是闭合回路。 以同样的方式绘制2个眼睛外框。 使用椭…

应用层协议:HTTP

目录 HTTP&#xff1a;超文本传输协议 1.1 HTTP报文 1.1.1 请求报文 1.1.2 响应报文 1.2 HTTP请求过程和原理 1.2.1 请求过程 1、域名&#xff08;DNS&#xff09;解析 2、建立TCP连接&#xff08;三次握手&#xff09; 3、发送HTTP请求 4、服务器处理请求 5、返回H…

复习——C++

1、scanf和scanf_s区别 2、取地址&#xff0c;输出 char ba; char* p&b; cout<<*p; cout<<p; p(char*)"abc"; cout<<*p; cout<<p; cout<<(void*)p; 取地址&#xff0c;把b的地址给p 输出*p&#xff0c;是输出p的空间内的值…

SPI通信协议(软件SPI读取W25Q64)

SPI通信协议 文章目录 SPI通信协议1.SPI通信2.SPI硬件和软件规定2.1SPI硬件电路2.2移位示意图2.3SPI基本时序单元2.3.1起始和终止条件2.3.2交换一个字节&#xff08;模式1&#xff09; 2.4SPI波形分析&#xff08;辅助理解&#xff09;2.4.1发送指令2.4.2指定地址写2.4.3指定地…

JavaWeb:前后端分离开发-部门管理

今日内容 前后端分离开发 准备工作 页面布局 整体布局-头部布局 Container 布局容器 左侧布局 资料\04. 基础文件\layout/index.vue <script setup lang"ts"></script><template><div class"common-layout"><el-containe…

字节开源FlowGram:AI时代可视化工作流新利器

字节终于开源“扣子”同款引擎了&#xff01;FlowGram&#xff1a;AI 时代的可视化工作流利器 字节FlowGram创新性地融合图神经网络与多模态交互技术&#xff0c;构建了支持动态拓扑重构的可视化流程引擎。该系统通过引入 f ( G ) ( V ′ &#xff0c; E ′ ) f(\mathcal{G})…