OpenCV C/C++ 视频播放器 (支持调速和进度控制)
OpenCV C/C++ 视频播放器 (支持调速和进度控制)
本文将引导你使用 C++ 和 OpenCV 库创建一个功能稍复杂的视频播放器。该播放器不仅能播放视频,还允许用户通过滑动条来调整播放速度(加速/减速)以及控制视频的播放进度。
使用opencv打开不会压缩画质,你看起来的效果和其他播放器打开的不一样,会觉得很高清
目录
- 功能简介
- 先决条件
- 核心组件
- 代码实现
- 全局变量与结构
- 滑动条回调函数
- 主函数
main
- 完整代码
- 编译和运行
- 使用说明
- 代码解释
- 速度控制
- 进度控制
- 暂停/播放
- 可能的改进
- 总结
功能简介
- 播放本地视频文件。
- 通过滑动条调整播放速度(例如,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
。
核心组件
cv::VideoCapture
: 用于打开和读取视频帧。cv::imshow
: 用于在窗口中显示视频帧。cv::createTrackbar
: 用于创建速度控制和进度控制的滑动条。- 回调函数: 用于响应滑动条数值的变化。
- 主循环: 控制视频的读取、显示、延迟和用户输入。
代码实现
我们将逐步构建代码。
全局变量与结构
为了在回调函数和主循环之间共享数据,我们会使用一些全局变量。对于更大型的应用,通常会封装在一个类中。
#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) { // 防止速度过低或为0g_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_progress
和 cv::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至少需要1mschar 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.0xon_trackbar_speed(100, 0); // 手动调用一次以初始化g_current_fps_multipliercv::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 (推荐):
- 将上述代码保存为
video_player.cpp
。 - 创建
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})
- 编译:
mkdir build cd build cmake .. make # 或者ninja, 或者在Visual Studio中构建项目
- 运行:
如果没有在命令行提供路径,程序会提示你输入。./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
增大,播放减慢。
- 倍率 > 1.0 (加速):
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_paused
为true
:- 主循环不读取新的视频帧 (
cap.read()
不被调用)。 cv::imshow
持续显示暂停时捕获的最后一帧g_current_frame_for_pause
。
- 主循环不读取新的视频帧 (
- 如果
g_paused
为false
:- 正常读取和显示视频帧。
可能的改进
- 更精确的进度条拖动检测: 当前实现中,程序更新进度条也可能触发回调。可以通过更复杂的事件处理或自定义UI控件来区分用户拖动和程序更新。
- 显示时间码: 在窗口上显示 “当前时间 / 总时间” 而不是帧号。
- 音量控制和音频播放: OpenCV 主要处理视频。播放音频需要额外的库,如 SDL、PortAudio,或者使用 FFmpeg 更底层的 API。
- 逐帧步进: 添加按钮或快捷键实现向前/向后逐帧播放。
- 错误处理: 更详细的错误报告和处理。
- UI美化: 使用 Qt 或其他 GUI 框架替换 OpenCV HighGUI 可以创建更美观和功能丰富的界面。
- 播放列表: 支持加载和管理多个视频文件。
总结
这个项目展示了如何使用 OpenCV 创建一个具有基本播放控制功能的视频播放器。通过滑动条,用户可以方便地控制播放速度和进度,并通过键盘快捷键进行暂停和退出。这是一个很好的练习,可以帮助你熟悉 OpenCV 的视频处理和简单的 GUI 交互。
相关文章:

OpenCV C/C++ 视频播放器 (支持调速和进度控制)
OpenCV C/C 视频播放器 (支持调速和进度控制) 本文将引导你使用 C 和 OpenCV 库创建一个功能稍复杂的视频播放器。该播放器不仅能播放视频,还允许用户通过滑动条来调整播放速度(加速/减速)以及控制视频的播放进度。 使用opencv打开不会压缩画…...

【Linux庖丁解牛】—自定义shell的编写!
1. 打印命令行提示符 在我们使用系统提供的shell时,每次都会打印出一行字符串,这其实就是命令行提示符,那我们自定义的shell当然也需要这一行字符串。 这一行字符串包含用户名,主机名,当前工作路径,所以&a…...
C++抽象类与多态实战解析
这段 C 代码演示了 抽象类(Abstract Class) 和 多态(Polymorphism) 的使用,它定义了一个表示教师的抽象基类 Teacher,并派生出两个具体的子类:EnglishTeacher(英语老师)和…...
OpenAI API 流式传输
OpenAI API 流式传输教程 🌊 本教程将详细解释 OpenAI API 如何进行数据流式传输,从基本的文本块到复杂的工具调用指令。流式传输允许你逐步从模型接收数据,这对于构建响应灵敏的用户界面和处理长输出非常有用。 1. 基础知识:Ser…...
嵌入式分析利器:DuckDB与SqlSugar实战
一、DuckDB 的核心特性与适用场景 DuckDB 是一款 嵌入式分析型数据库(OLAP) ,专为高效查询设计,主要特点包括: 列式存储与向量化引擎 数据按列存储,提升聚合统计效率(如 SUM/AVG…...
嵌入式学习笔记 - freeRTOS任务设计要点
一 中断函数中不允许操作任务 因为中断函数使用的上下文环境是MSP环境,而非PSP环境,不允许挂起任务,不允许阻塞任务的任何操作。 可以使用FromISR函数进行操作。 二 中断的频率与处理时间 中断的处理时间要远低于任务的运行时间ÿ…...

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

12:点云处理—调平,角度,平面度,高度,体积
1.调平 2.夹角、平面度 3.高度、体积...
Marketo 集成 8x8 Connect 短信 API 指南
一、🔍 项目背景与目标 在营销自动化流程中,需要在用户完成特定行为(如填写表单、完成注册)后,自动发送一条短信进行提醒、欢迎或验证。 Marketo 原生不具备短信发送能力,但支持通过 Webhook 集成第三方 A…...

【Docker 从入门到实战全攻略(二):核心概念 + 命令详解 + 部署案例】
5. Docker Compose Docker Compose 是一个用于定义和运行多容器 Docker 应用的工具。通过一个 YAML 文件来配置应用服务,然后使用一个命令即可创建并启动所有服务。 基本命令 docker-compose up # 创建并启动所有服务 docker-compose down # 停止并移除容器、网络等…...
Elasticsearch索引(Index)介绍,它与数据库中的表有什么区别?
在Elasticsearch(ES)中,索引(Index)是存储和组织文档(Document)的逻辑容器,类似于关系型数据库(如MySQL)中的“数据库(Database)”或“表(Table)”,但设计理念和实现机制有显著差异。以下从定义、核心特性、与数据库表的对比三方面详细解析。 一、索引的定义与…...
Elasticsearch中什么是分析器(Analyzer)?它由哪些组件组成?
在Elasticsearch(ES)中,分析器(Analyzer)是处理文本的核心组件,负责将原始文本转换为适合索引和搜索的词项(Term)。它直接影响搜索的准确性和性能,是构建高效搜索系统的关键。 一、分析器的核心作用 1. 分词(Tokenization):将文本拆分为独立的词(Token)。 例如…...
使用 SseEmitter 实现 Spring Boot 后端的流式传输和前端的数据接收
1.普通文本消息的发送和接收 GetMapping("/stream")public SseEmitter streamResponse() {SseEmitter emitter new SseEmitter(0L); // 0L 表示永不超时Executors.newSingleThreadExecutor().execute(() -> {try {for (int i 1; i < 5; i) {emitter.send(&q…...
.net Avalonia 在centos部署
.NET Avalonia 在 CentOS 部署指南 在跨平台应用开发中,.NET Avalonia 凭借其强大的功能和灵活性受到了广泛关注。而将基于 .NET Avalonia 开发的应用程序部署到 CentOS 系统上,是很多开发者会面临的任务。下面就为大家详细介绍在 CentOS 上部署 .NET A…...
MyBatis深度解析:XML/注解配置与动态SQL编写实战
引言 在现代Java企业级应用开发中,MyBatis作为一款优秀的持久层框架,因其灵活性和易用性广受开发者喜爱。相比Hibernate等全自动ORM框架,MyBatis提供了更接近SQL的开发体验,同时又不失面向对象的优雅。本文将深入探讨MyBatis的核…...
面试经验 对常用 LLM 工具链(如 LlamaFactory)的熟悉程度和实践经验
面试场景: 你正在面试一个大型语言模型(LLM)工程师或研究员的职位,面试官想了解你对常用 LLM 工具链(如 LlamaFactory)的熟悉程度和实践经验。 面试经验分享:LlamaFactory-CLI 工具实践 面试官…...

【conda配置深度学习环境】
好的!我们从头开始配置一个基于Conda的虚拟环境,覆盖深度学习(如PyTorch)和传统机器学习(如XGBoost),并适配你的显卡(假设为NVIDIA,若为AMD请告知)。以下是完…...

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

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

【前端】性能优化和分类
本页知识点参考:https://zhuanlan.zhihu.com/p/514222781 1. 加载性能优化 1.1 网站性能优化 content方法: 1)减少HTTP请求:合并文件,CSS精灵,inline Image 2)减少DNS查询:DNS缓存&…...

PPO和GRPO算法
verl 是现在非常火的 rl 框架,而且已经支持了多个 rl 算法(ppo、grpo 等等)。 过去对 rl 的理解很粗浅(只知道有好多个角色,有的更新权重,有的不更新),也曾硬着头皮看了一些论文和知…...
ceph 对象存储用户限额满导致无法上传文件
查看日志 kl logs -f rook-ceph-rgw-my-store-a-5cc4c4d5b5-26n6j|grep -i error|head -1Defaulted container "rgw" out of: rgw, log-collector, chown-container-data-dir (init) debug 2025-05-30T19:44:11.573+0000 7fa7b7a6d700...

rk3588 上运行smolvlm-realtime-webcam,将视频转为文字描述
smolvlm-realtime-webcam 是一个开源项目,结合了轻量级多模态模型 SmolVLM 和本地推理引擎 llama.cpp,能够在本地实时处理摄像头视频流,生成自然语言描述, 开源项目地址 https://github.com/ngxson/smolvlm-realtime-webcamhttps…...
某航参数逆向及设备指纹分析
文章目录 1. 写在前面2. 接口分析3. 加密分析4. 算法还原5. 设备指纹风控分析与绕过【🏠作者主页】:吴秋霖 【💼作者介绍】:擅长爬虫与JS加密逆向分析!Python领域优质创作者、CSDN博客专家、阿里云博客专家、华为云享专家。一路走来长期坚守并致力于Python与爬虫领域研究…...
SQL思路解析:窗口滑动的应用
目录 🎯 问题目标 第一步:从数据中我们能直接得到什么? 第二步:我们想要的“7天窗口”长什么样? 第三步:SQL 怎么表达“某一天的前六天”? 🔍JOIN 比窗口函数更灵活 第四步&am…...

Rust 学习笔记:Box<T>
Rust 学习笔记:Box Rust 学习笔记:Box<T\>Box\<T> 简介使用 Box\<T\> 在堆上存储数据启用带有 box 的递归类型关于 cons 列表的介绍计算非递归类型的大小使用 Box\<T\> 获取大小已知的递归类型 Rust 学习笔记:Box<…...
C# 从 ConcurrentDictionary 中取出并移除第一个元素
C# 从 ConcurrentDictionary 中取出并移除第一个元素 要从 ConcurrentDictionary<byte, int> 中取出并移除第一个元素,需要结合 遍历 和 原子移除操作。由于 ConcurrentDictionary 是无序集合,"第一个元素" 通常是指最早添加的元素&…...

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

NLP学习路线图(二十二): 循环神经网络(RNN)
在自然语言处理(NLP)的广阔天地中,序列数据是绝对的核心——无论是流淌的文本、连续的语音还是跳跃的时间序列,都蕴含着前后紧密关联的信息。传统神经网络如同面对一幅打散的拼图,无法理解词语间的顺序关系,…...

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