opencv
-
opencv视频流
环境准备 本文通过采集USB摄像头来示例说明 export LD_LIBRARY_PATH=/mnt/extsd/usr/lib:$LD_LIBRARY_PATH #指定库的路径 cat /sys/devices/platform/soc/usbc0/usb_host #激活USB host 摄像头采集 摄像头相关的主要使用的是VideoCapture类。 打开摄像头 cv::VideoCapture cap; // 创建视频捕获对象 cap.open(0); // 打开默认摄像头(通常为/dev/video0) if (!cap.isOpened()) { std::cerr << "无法打开视频设备" << std::endl; return -1; } 设置参数 cap.set(cv::CAP_PROP_FRAME_WIDTH, 640); // 设置宽度 cap.set(cv::CAP_PROP_FRAME_HEIGHT, 480); // 设置高度 cap.set(cv::CAP_PROP_FPS, 30); // 设置帧率 采集数据 cv::Mat frame; while (true) { cap >> frame; // 捕获一帧 if (frame.empty()) { std::cerr << "捕获帧失败" << std::endl; continue; } // 在此处理帧数据... usleep(1000000 / frame_rate); // 30ms延迟,控制帧率 } 推流显示 主要是打开/dev/fb0,然后写入数据。 获取显示参数 通过ioctl的方式,获取显示分辨率,像素位深(每个像素的占用内存大小),以便将采集到的摄像头数据调整为合适的尺寸进行显示。 struct framebuffer_info get_framebuffer_info(const char* framebuffer_device_path) { struct framebuffer_info info; struct fb_var_screeninfo screen_info; int fd = -1; fd = open(framebuffer_device_path, O_RDWR); if (fd >= 0) { if (!ioctl(fd, FBIOGET_VSCREENINFO, &screen_info)) { info.xres_virtual = screen_info.xres_virtual; //虚拟水平的分辨率,包含不可见区域,即虚拟宽度 info.bits_per_pixel = screen_info.bits_per_pixel; //每个像素的位深,即像素大小,如16/24/32等 info.xres = screen_info.xres; //实际水平分辨率,即实际宽度 info.yres = screen_info.yres; //垂直分辨率,即实际高度 } } return info; }; 尺寸调整 cv::resize(frame, frame, cv::Size(fb_info.xres, fb_info.yres)); 从/dev/fb0获取的实际分辨率进行调整尺寸。 格式转换 写入前,先将采集的视频帧转换为屏幕显示支持的格式,如RGB565,RGBA等。 switch (framebuffer_depth) { case 16: //转换为RGB565格式输出 cv::cvtColor(frame, framebuffer_compat, cv::COLOR_BGR2BGR565); break; case 32: //转换为RGBA格式输出,添加一个alpha通道 std::vector<cv::Mat> split_bgr; cv::split(frame, split_bgr);//将BGR分离为3个通道,存储到split_bgr中。 split_bgr.push_back(cv::Mat(frame_size, CV_8UC1, cv::Scalar(255))); //创建一个全白的alpha通道矩阵并进行添加到一个新的通道 cv::merge(split_bgr, framebuffer_compat); //使用merge将3个颜色通道和alpha通道进行合并为RGBA四通道。 break; } 写入显示 ofs.seekp(0); //先进行定位 ofs.write(reinterpret_cast<char*>(framebuffer_compat.ptr(0)), framebuffer_compat.total() * framebuffer_compat.elemSize()); 也可以一行一行的写 for (int y = 0; y < frame_size.height; y++) { ofs.seekp(y * framebuffer_width * 2); ofs.write(reinterpret_cast<char*>(framebuffer_compat.ptr(y)), frame_size.width * 2); } 示例程序 #include <fcntl.h> #include <fstream> #include <iostream> #include <linux/fb.h> #include <signal.h> #include <stdint.h> #include <sys/ioctl.h> #include <unistd.h> #include <opencv2/opencv.hpp> static cv::VideoCapture cap; struct framebuffer_info { uint32_t bits_per_pixel; uint32_t xres_virtual; uint32_t xres; uint32_t yres; }; struct framebuffer_info get_framebuffer_info(const char* framebuffer_device_path) { struct framebuffer_info info; struct fb_var_screeninfo screen_info; int fd = -1; fd = open(framebuffer_device_path, O_RDWR); if (fd >= 0) { if (!ioctl(fd, FBIOGET_VSCREENINFO, &screen_info)) { info.xres_virtual = screen_info.xres_virtual; info.bits_per_pixel = screen_info.bits_per_pixel; info.xres = screen_info.xres; info.yres = screen_info.yres; } } return info; }; /* Signal handler */ static void terminate(int sig_no) { printf("Got signal %d, exiting ...\n", sig_no); cap.release(); exit(1); } int main(int, char**) { const int frame_width = 720; const int frame_height = 1280; const int frame_rate = 30; framebuffer_info fb_info = get_framebuffer_info("/dev/fb0"); std::cout << "xres virtua" << fb_info.xres_virtual << std::endl; std::cout << "bits per pixel" << fb_info.bits_per_pixel << std::endl; cap.open(0); if (!cap.isOpened()) { std::cerr << "Could not open video device." << std::endl; return 1; } std::cout << "Successfully opened video device." << std::endl; std::cout << "Display resolution:" << fb_info.xres << "x" << fb_info.yres << std::endl; cap.set(cv::CAP_PROP_FRAME_WIDTH, frame_width); cap.set(cv::CAP_PROP_FRAME_HEIGHT, frame_height); cap.set(cv::CAP_PROP_FPS, frame_rate); std::ofstream ofs("/dev/fb0"); cv::Mat frame; cv::Mat trams_temp_fream; cv::Mat yuv_frame; while (true) { cap >> frame; if (frame.depth() != CV_8U) { std::cerr << "Not 8 bits per pixel and channel." << std::endl; } else if (frame.channels() != 3) { std::cerr << "Not 3 channels." << std::endl; } else { //cv::transpose(frame, frame); //cv::flip(frame, frame, 0); cv::resize(frame, frame, cv::Size(fb_info.xres, fb_info.yres)); int framebuffer_width = fb_info.xres_virtual; int framebuffer_depth = fb_info.bits_per_pixel; cv::Size2f frame_size = frame.size(); cv::Mat framebuffer_compat; switch (framebuffer_depth) { case 16: cv::cvtColor(frame, framebuffer_compat, cv::COLOR_BGR2BGR565); break; case 32: std::vector<cv::Mat> split_bgr; cv::split(frame, split_bgr); split_bgr.push_back(cv::Mat(frame_size, CV_8UC1, cv::Scalar(255))); cv::merge(split_bgr, framebuffer_compat); break; } ofs.seekp(0); ofs.write(reinterpret_cast<char*>(framebuffer_compat.ptr(0)), framebuffer_compat.total() * framebuffer_compat.elemSize()); usleep(1000000 / frame_rate); } } } -
opencv图像处理
图像滤波 图像的滤波核心是使用一个小的矩阵(滤波器或卷积核)在图像上进行滑动卷积,将计算得到的结果作为目标像素的值。 均值滤波 cv::blur(InputArray src, OutputArray dst, Size ksize, Point anchor = Point(-1,-1), int borderType = BORDER_DEFAULT); src:输入的原始图像,支持Mat格式 dst:经过滤波处理的图像 ksize:滤波器大小,通常为2D大小,表示滤波的宽度和高度。 anchor:锚点指定窗口内的参考点,默认是Point(-1,-1)表示窗口中心点 boarderType:边界类型,用来处理边界参数的外推方式。 cv::Mat src = cv::imread("image.jpg", cv::IMREAD_GRAYSCALE); cv::Mat dst; cv::blur(src, dst, cv::Size(3, 3)); // 3x3的均值滤波 高斯滤波 void cv::GaussianBlur( const cv::Mat& src, cv::Mat& dst, cv::Size ksize, double sigmaX, double sigmaY = 0, int borderType = cv::BORDER_DEFAULT ); sigmaX:X方向的标准差,决定模糊的程度,值越大,模糊效果越强。 sigmaY:Y方向的标准差,默认是0,表示与X方向相同。 cv::GaussianBlur(src, dst, cv::Size(5, 5), 0); // 5x5的高斯滤波 中值滤波 void medianBlur(InputArray src, OutputArray dst, int ksize); ksize: 滤波器窗口大小 示例 cv::medianBlur(src, dst, 5); // 5x5的中值滤波 示例 下面示例,将图片使用高斯噪音处理,然后再使用滤波器进行模糊,噪音就可以看起来过滤掉。 #include <fcntl.h> #include <fstream> #include <iostream> #include <linux/fb.h> #include <signal.h> #include <stdint.h> #include <sys/ioctl.h> #include <unistd.h> #include <opencv2/opencv.hpp> using namespace cv; using namespace std; void add_gaussian_noise(Mat& image, double mean, double stddev) { Mat noise(image.size(), image.type()); randn(noise, mean, stddev); image = image + noise; } void display_image(const Mat& image, int framebuffer_width = 720) { static std::ofstream ofs("/dev/fb0"); cv::Mat framebuffer; cv::cvtColor(image, framebuffer, cv::COLOR_BGR2BGRA); cv::Size2f frame_size = framebuffer.size(); for (int y = 0; y < frame_size.height; y++) { ofs.seekp(y * framebuffer_width * 4); ofs.write(reinterpret_cast<char*>(framebuffer.ptr(y)), frame_size.width * 4); } } int main(int, char**) { Mat image = imread("test1.jpg", IMREAD_COLOR); if (image.empty()) { cerr << "Error: Could not open or find the image!" << endl; return -1; } double mean = 2.0; double stddev = 30.0; add_gaussian_noise(image, mean, stddev); display_image(image); cv::imwrite("noise.jpg", image); usleep(1000 * 1000 * 2); Mat dst; cv::GaussianBlur(image, dst, cv::Size(3, 3), 1); //cv::medianBlur(image, dst, 5); //cv::blur(src, dst, cv::Size(3, 3)); display_image(dst); cv::imwrite("dst.jpg", dst); } 如下图,中间是加了噪声的,最后一张是通过高斯滤波处理之后的。 图像形态学 膨胀与腐蚀 膨胀操作会增加图像中的白色区域或前景区域,使得图像中的对象或结构变得更大。具体来说,它会将图像中的每个像素点扩展到其邻域像素中(根据结构元素的大小和形状)。这意味着如果结构元素的一部分重叠在一个前景区域内,该区域就会被扩展。使白色区域扩展,物体变大。 cv2.dilate(src, dst=None, kernel, anchor=(-1, -1), iterations=1, borderType=cv2.BORDER_CONSTANT, borderValue=0) iteration:膨胀迭代次数,越大效果越明显 borderType:边界填充类型 borderValue:边界填充值,默认0是黑色 cv::Mat kernel = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(3, 3)); //创建一个3*3的矩形结构元素 cv::dilate(src, dst, kernel); 腐蚀操作的效果与膨胀相反,它会减少图像中的白色区域或前景区域,使得图像中的对象变得更小。腐蚀操作会检查每个像素的邻域,如果邻域内的所有像素都是前景像素(白色),该像素才保持前景,否则变为背景(黑色)。使白色区域收缩,物体变小。 cv::Mat kernel = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(3, 3)); cv::erode(src, dst, kernel); 以下是示例 #include <fcntl.h> #include <fstream> #include <iostream> #include <linux/fb.h> #include <signal.h> #include <stdint.h> #include <sys/ioctl.h> #include <unistd.h> #include <opencv2/opencv.hpp> void display_image(const cv::Mat& image, int framebuffer_width = 720) { static std::ofstream ofs("/dev/fb0"); if (!ofs) { std::cerr << "Error: Could not open framebuffer device!" << std::endl; return; } cv::Mat framebuffer; if (image.channels() == 1) { cv::cvtColor(image, framebuffer, cv::COLOR_GRAY2BGRA); } else if (image.channels() == 3) { cv::cvtColor(image, framebuffer, cv::COLOR_BGR2BGRA); } else if (image.channels() == 4) { image.copyTo(framebuffer); } else { std::cerr << "Error: Unsupported image format!" << std::endl; return; } cv::Size2f frame_size = framebuffer.size(); if (frame_size.width > framebuffer_width) { // 计算新的宽高,保持图像的纵横比 float aspect_ratio = frame_size.height / frame_size.width; int newWidth = framebuffer_width; int newHeight = static_cast<int>(newWidth * aspect_ratio); // 调整图像大小 cv::Mat resizedImage; cv::resize(framebuffer, resizedImage, cv::Size(newWidth, newHeight)); framebuffer = resizedImage; frame_size = resizedImage.size(); } for (int y = 0; y < frame_size.height; y++) { ofs.seekp(y * framebuffer_width * 4); ofs.write(reinterpret_cast<char*>(framebuffer.ptr(y)), frame_size.width * 4); } } int main(int, char**) { cv::Mat image = imread("test1.jpg", cv::IMREAD_COLOR); if (image.empty()) { std::cerr << "Error: Could not open or find the image!" << std::endl; return -1; } double mean = 2.0; double stddev = 30.0; display_image(image); usleep(1000 * 1000 * 1); cv::Mat dst; cv::Mat kernel = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(5, 5)); cv::dilate(image, dst, kernel); display_image(dst); usleep(1000 * 1000 * 1); cv::erode(image, dst, kernel); display_image(dst); } 阈值化 阈值(Thresholding)是一种常见的图像分割方法,用于将灰度图像转换为二值图像。通过设置一个阈值,将像素值高于该阈值的区域设为白色(255),低于阈值的区域设为黑色(0),从而简化图像内容,便于后续处理。 二值化阈值:二值化是将图像中的像素值根据设定的阈值分为两类,通常用于简单的图像分割。 cv::threshold(src, dst, thresh, maxval, type); 示例:cv::threshold(src, dst, 127, 255, cv::THRESH_BINARY); thresh: 阈值,决定像素分类的分界线。 maxval:根据type选择,满足条件设置为最大值。 type: 阈值类型 自适应阈值:根据图像的局部区域动态计算阈值,适用于光照不均匀的图像。 cv::adaptiveThreshold( src, dst, maxValue, adaptiveMethod, thresholdType, blockSize, C); maxVaule:二值化的最大值,一般为255 adaptiveMethod: ADAPTIVE_THRESH_MEAN_C是使用邻域的均值作为阈值,ADAPTIVE_THRESH_GAUSSIAN_C :使用邻域的加权均值(高斯加权)作为阈值。 thresholdType:阈值类型,通常使用 THRESH_BINARY 或 THRESH_BINARY_INV。 blockSize:邻域的大小,必须是奇数(例如 3 , 5 , 7 , ...),该窗口在图像上滑动。 C:调整值,用于从计算的阈值中减去。调整结果的灵敏度。 cv::adaptiveThreshold(src, dst, 255, cv::ADAPTIVE_THRESH_MEAN_C, cv::THRESH_BINARY, 11, 2); 开闭运算、顶帽、黑帽 开运算:开运算是先进行腐蚀操作再进行膨胀操作。它通常用于去除小的噪点或小的物体,同时保留较大区域的结构。 闭运算:闭运算是先进行膨胀操作再进行腐蚀操作。它用于去除小的黑色区域(如小孔洞或空隙),并连接物体之间的细小裂缝。 顶帽:顶帽是开运算的结果与原图像之间的差异,主要用于提取比背景更亮的区域或小的亮点。 黑帽:黑帽是闭运算的结果与原图像之间的差异,主要用于提取比背景更暗的区域或小的暗点。 void cv::morphologyEx( InputArray src, OutputArray dst, int op, InputArray kernel, Point anchor = Point(-1, -1), int iterations = 1, int borderType = BORDER_CONSTANT, const Scalar&borderValue=morphologyDefaultBorderValue() ); kernel:结构元素,通常使用 cv::getStructuringElement() 生成。 op:形态学操作类型。 下面是op的类型 cv:MORPH_OPEN:开运算。 cv::MORPH_CLOSE:闭运算。 cv::MORPH_GRADIENT: 形态学梯度(膨胀 - 腐蚀),提取物体边缘。 cv::MORPH_TOPHAT :顶帽运算(原图 - 开运算结果),提取比背景亮的区域。 cv::MORPH_BLACKHAT 黑帽运算(闭运算结果 - 原图),提取比背景暗的区域。 示例 示例程序如下: int main(int, char**) { cv::Mat image = imread("test1.jpg", cv::IMREAD_GRAYSCALE); if (image.empty()) { std::cerr << "Error: Could not open or find the image!" << std::endl; return -1; } cv::Mat binary_img; cv::threshold(image, binary_img, 128, 255, cv::THRESH_BINARY); cv::Mat kernel = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(5, 5)); cv::Mat dst; cv::Mat opened_img, closed_img, tophat_img, blackhat_img; cv::morphologyEx(binary_img, opened_img, cv::MORPH_OPEN, kernel); cv::morphologyEx(binary_img, closed_img, cv::MORPH_CLOSE, kernel); cv::morphologyEx(binary_img, tophat_img, cv::MORPH_TOPHAT, kernel); cv::morphologyEx(binary_img, blackhat_img, cv::MORPH_BLACKHAT, kernel); display_image(image); usleep(1000 * 1000 * 2); display_image(binary_img); usleep(1000 * 1000 * 2); display_image(closed_img); usleep(1000 * 1000 * 2); display_image(tophat_img); usleep(1000 * 1000 * 2); display_image(blackhat_img); } 图像边缘检测 canny Canny边缘检测是一个多阶段的图像处理算法,用于提取图像中的边缘。它的目标是识别图像中强度变化较大的区域,即边缘 int main(void) { cv::Mat image = cv::imread("test1.jpg", cv::IMREAD_GRAYSCALE); if (image.empty()) { std::cerr << "Error: Could not open image!" << std::endl; return -1; } // 进行 Canny 边缘检测 cv::Mat edges; cv::Canny(image, edges, 100, 200); // 低阈值 100,高阈值 200 display_image(edges); return 0; } 下面是处理的前后效果。 霍夫变换 霍夫变换(Hough Transform)是一种用于检测图像中的几何形状(如直线、圆等)的方法。它主要用于图像中的特征检测,特别是在噪声较大的图像中,可以有效地进行形状的识别。霍夫变换的基本思想是将图像中的边缘点通过一种数学映射,转化到参数空间,然后在参数空间中寻找对应的曲线(或直线、圆等)。 霍夫线变换: 检测线。 cv2.HoughLinesP(image, rho, theta, threshold, minLineLength, maxLineGap) image: 输入图像,通常是经过边缘检测(如 Canny 边缘检测)后的二值图像。 rho: 直线距离精度,单位是像素,通常设置为 1。 theta: 角度精度,单位是弧度,通常设置为 np.pi / 180(即 1°)。 threshold: 最小投票数,当某一候选直线在参数空间中的投票数大于此阈值时,认为检测到直线。 minLineLength: 最小直线长度。只有长度大于该值的直线才会被返回。 maxLineGap: 最大直线间隙。如果两段直线之间的间隙小于该值,则认为它们是同一条直线的两部分 int main(void) { cv::Mat img = cv::imread("test1.jpg"); if (img.empty()) { std::cerr << "Error: Could not open image!" << std::endl; return -1; } // 图像预处理:边缘检测 cv::Mat edges; cv::Canny(img, edges, 50, 150); // 使用 Canny 边缘检测 display_image(edges); usleep(1000 * 1000); // 使用 HoughLinesP 进行霍夫变换(概率霍夫变换) std::vector<cv::Vec4i> lines; cv::HoughLinesP(edges, lines, 1, CV_PI / 180, 50, 50, 10); // 参数为 (边缘图像, 输出的直线, ρ精度, θ精度, 阈值, 最小线段长度, 最大线段间隙) // 绘制检测到的直线 cv::Mat result = img.clone(); //cv::cvtColor(result, result, cv::COLOR_GRAY2BGR); // 将灰度图转为彩色图以显示彩色直线 for (size_t i = 0; i < lines.size(); i++) { cv::Vec4i l = lines[i]; cv::line(result, cv::Point(l[0], l[1]), cv::Point(l[2], l[3]), cv::Scalar(255, 0, 0), 2); // 绘制直线 } display_image(result); return 0; } 上面的示例中,先使用canny进行边缘处理,然后送入到霍夫变换中进行检测,最后绘制成直线。下面是检测后的效果。 霍夫圆变换: 检测圆。 cv2.HoughCircles(image, method, dp, minDist, param1, param2, minRadius, maxRadius) image: 输入图像,通常是灰度图像。 method: 霍夫变换的检测方法。对于 HoughCircles,一般使用 cv2.HOUGH_GRADIENT。 dp: 分辨率反比,表示霍夫空间的分辨率与图像分辨率的比例。通常设置为 1。 minDist: 设定圆心之间的最小距离,防止检测到相互靠得太近的圆。 param1: 边缘检测的高阈值(Canny 边缘检测的高阈值)。 param2: 圆的中心检测阈值,较小的值会检测到更多的圆,较大的值会检测较少的圆。 minRadius: 最小圆半径(单位:像素)。 maxRadius: 最大圆半径(单位:像素)。 int main(void) { cv::Mat img = cv::imread("test1.jpg"); if (img.empty()) { std::cerr << "Error: Could not open image!" << std::endl; return -1; } // 进行边缘检测 cv::Mat edges; cv::Canny(img, edges, 50, 150); display_image(edges); usleep(1000 * 1000); // 存储检测到的圆 std::vector<cv::Vec3f> circles; // 使用霍夫圆变换检测圆 cv::HoughCircles(edges, circles, cv::HOUGH_GRADIENT, 1, 23, 80, 52, 27, 100); // 在原图上绘制检测到的圆 cv::Mat result = img.clone(); for (size_t i = 0; i < circles.size(); i++) { cv::Point center(cvRound(circles[i][0]), cvRound(circles[i][1])); int radius = cvRound(circles[i][2]); // 绘制圆心 cv::circle(result, center, 3, cv::Scalar(0, 255, 0), -1); // 绘制圆 cv::circle(result, center, radius, cv::Scalar(255, 0, 0), 3); } display_image(result); return 0; } 同样是先使用canny检测进行处理,然后再使用霍夫圆进行检测圆,最后试用cv::circle进行绘制。 -
opencv基础操作
图像的读取和显示 读写图像 cv::Mat image = cv::imread("image.jpg", cv::IMREAD_COLOR); 使用imread函数读取图像,第一个参数是图像文件的路径,第二个参数是解释图像的颜色和格式(如彩色图像、灰度图像等)。第二个参数可省略,默认是cv::IMREAD_COLOR,以彩色图像读取。 cv::imwrite("output.jpg", image); 使用imwrite存储图像。 显示 使用imshow基于图像用户界面的显示方式,这种方式用于桌面的计算机,直接在屏幕上展示图像。 cv::imshow("Window title", image); cv::waitKey(0); // 等待按键按下 通过/dev/fb0节点写入,这种方式一般应用在嵌入式平台上,写入屏幕的驱动中。 std::ofstream ofs("/dev/fb0"); cv::Mat framebuffer; 定义一个存储图像像素数据的容器 cv::cvtColor(frame, framebuffer, cv::COLOR_BGR2BGR565); 将图像格式进行转换为与驱动支持的格式BGR565 ofs.write(reinterpret_cast<char*>(framebuffer.ptr(0)), framebuffer.total() * framebuffer.elemSize()); //现将framebuffer强制转换为char*格式,确保内存数据按字节进行处理。 //framebuffer.total()返回的是像素总元素数量,如果framebuffer是多通道,那就返回总数 //framebuffer.elemSize()返回的是每个像素字节大小。 //需要注意的是使用一次性写入,要注意fb的位置,每次写需要重新定位 还有另外一种写法,每一行每一行的刷新 cv::Size2f frame_size = frame.size(); cv::cvtColor(frame, framebuffer_compat, cv::COLOR_BGR2BGR565); for (int y = 0; y < frame_size.height; y++) { ofs.seekp(y * framebuffer_width * 2); //定位一行的位置,framebuffer_width是宽,即每行多少个像素 //*2是每个像素多少个字节,BGR565是2个字节。 ofs.write(reinterpret_cast<char*>(framebuffer_compat.ptr(y)), frame_size.width * 2); //写入一行数据,一行的数据量为frame_size.width * 2 } 下面是封装的示例函数 void display_image(const cv::Mat& image, int framebuffer_width = 720) { static std::ofstream ofs("/dev/fb0"); if (!ofs) { std::cerr << "Error: Could not open framebuffer device!" << std::endl; return; } cv::Mat framebuffer; if (image.channels() == 1) { cv::cvtColor(image, framebuffer, cv::COLOR_GRAY2BGRA); } else if (image.channels() == 3) { cv::cvtColor(image, framebuffer, cv::COLOR_BGR2BGRA); } else if (image.channels() == 4) { image.copyTo(framebuffer); } else { std::cerr << "Error: Unsupported image format!" << std::endl; return; } cv::Size2f frame_size = framebuffer.size(); if (frame_size.width > framebuffer_width) { // 计算新的宽高,保持图像的纵横比 float aspect_ratio = frame_size.height / frame_size.width; int newWidth = framebuffer_width; int newHeight = static_cast<int>(newWidth * aspect_ratio); // 调整图像大小 cv::Mat resizedImage; cv::resize(framebuffer, resizedImage, cv::Size(newWidth, newHeight)); framebuffer = resizedImage; frame_size = resizedImage.size(); } for (int y = 0; y < frame_size.height; y++) { ofs.seekp(y * framebuffer_width * 4); ofs.write(reinterpret_cast<char*>(framebuffer.ptr(y)), frame_size.width * 4); } } 示例 int main() { cv::Mat test_image = cv::imread("test.jpg"); if (test_image.empty()) { std::cerr << "Could not open the image" << std::endl; return -1; } std::ofstream ofs("/dev/fb0"); if (!ofs.is_open()) { std::cerr << "Failed to open framebuffer device." << std::endl; return -1; } // 将 BGR 图像转换为 BGRA 格式(RGB8888) cv::Mat framebuffer; cv::cvtColor(test_image, framebuffer, cv::COLOR_BGR2BGRA); ofs.write(reinterpret_cast<char*>(framebuffer.ptr(0)), framebuffer.total() * framebuffer.elemSize()); return 0; } 几何变换 这里的变换指的是缩放、旋转、平移、翻转。 裁剪与缩放 缩放 cv::Mat resizedImage; cv::resize(image, resizedImage, cv::Size(newWidth, newHeight)); 裁剪 Rect roi(100, 100, 200, 200); // (x, y, width, height) //先于x,y坐标开始,再按照width,height定义一个矩形框。 Mat croppedImage = image(roi); //将矩形框作用在image上,得到一个新的图像croppedImage 旋转 // cv::Point2f用于表示二维浮点坐标的类,下面是计算旋转中心点。 cv::Point2f center(image.cols / 2.0, image.rows / 2.0); // 用于计算二维旋转的仿射变换矩阵,45是旋转角度,1.0是缩放比例。 cv::Mat rotationMatrix = cv::getRotationMatrix2D(center, 45.0, 1.0); // 声明存储旋转后图像的变量 cv::Mat rotatedImage; // 执行仿射变换进行图像旋转 cv::warpAffine(image, rotatedImage, rotationMatrix, image.size()); 翻转 cv::Mat flippedImage; cv::flip(image, flippedImage, 1); // 1表示水平翻转,0表示垂直翻转 颜色变换 图像的颜色空间包括RGB、灰度和HSV等。 RGB:RGB是最常见的颜色空间,表示红、绿、蓝三个通道。 灰度:灰度图像只有一个通道,表示亮度。 HSV:HSV颜色空间表示色调(Hue)、饱和度(Saturation)和亮度(Value)。 颜色空间转换 RGB转灰度 cv::Mat grayImage; cv::cvtColor(image, grayImage, cv::COLOR_BGR2GRAY); RGB转HSV cv::Mat hsvImage; cv::cvtColor(image, hsvImage, cv::COLOR_BGR2HSV); RGB转RGB565 cv::Mat rgb565Image; cv::cvtColor(rgb565Image, framebuffer, cv::COLOR_BGR2BGR565); 通道分离 这里的通道指的是图像通道比如RGB的R/G/B 3个通道。通道分离的场景是分析每个颜色通道的像素值,比如在检测红色物体时,可以单独分析红色通道。另外还可以用做图像增强,对不同通道应用不同的增强算法,然后再合并。 通道分离 std::vector<cv::Mat> channels; //分离出的通道存储到channels中 cv::split(image, channels); 通道合并 cv::Mat mergedImage; cv::merge(channels, mergedImage); 示例代码 int main() { cv::Mat test_image = cv::imread("test.jpg"); if (test_image.empty()) { std::cerr << "Could not open the image" << std::endl; return -1; } double angle = 90; // 计算旋转中心点 cv::Point2f center(test_image.cols / 2.0, test_image.rows / 2.0); // 计算旋转矩阵 cv::Mat rotationMatrix = cv::getRotationMatrix2D(center, angle, 1.0); // 声明存储旋转后图像的变量 cv::Mat rotatedImage; // 执行仿射变换进行图像旋转 cv::warpAffine(test_image, rotatedImage, rotationMatrix, test_image.size()); std::ofstream ofs("/dev/fb0"); if (!ofs.is_open()) { std::cerr << "Failed to open framebuffer device." << std::endl; return -1; } // 将 BGR 图像转换为 BGRA 格式(RGB8888) cv::Mat framebuffer; cv::cvtColor(rotatedImage, framebuffer, cv::COLOR_BGR2BGRA); ofs.write(reinterpret_cast<char*>(framebuffer.ptr(0)), framebuffer.total() * framebuffer.elemSize()); return 0; } 总结 本章节总结opencv操作常用的数据结构。 Mat Mat对象的用途主要为存储图像、创建和操作多维矩阵。Mat数据类型分为两部分:信息头+指向像素数据的矩阵指针,信息头存储的是图像的尺寸、存储方法、存储地址。指向像素的矩阵指针为字面意思,即指向存储所有像素值的矩阵指针。 Mat A, C; //只创建信息头 A = imread(argv[1], IMREAD_COLOR); //为矩阵开辟内存空间。 Mat B(A); // Use the copy constructor C = A; // Assignment operator 如果在读取图像的时候只想获取图像中的部分图像(感兴趣的区域ROI),可以使用Rect和Range来标中数据。 Mat D (A, Rect(10, 10, 100, 100) ); // using a rectangle Mat E = A(Range::all(), Range(1,3)); // using row and column boundaries 其中Rect(10,10,100,100)前两位是坐标,后两位指定宽高,示例如下。 Range::all()选中所有的行,Range(1,3)选中1~3列。 Mat对象作为通用矩阵类和图像容器,容器中存储的是原始的像素值。在opencv中描述这些像素值有专门的数据格式。 Mat Img(640, 640, CV_8UC3); 上述中640*640大小的图像,存储的数据类型是CV_U8C3,这里的CV_U8C3格式为基本数据类型+通道数。"CV_"表示前缀,"8"表示每个像素值的位深比特数,有8/16/32/64bits,"U"表示数据类型为无符号,"C3"表示通道数RGB。关于通道一般有几种,示例如下: 1通道:灰度图像 3通道:RGB通道,图像由红、绿、蓝三色组成。 4通道:在RGB通道基础上,加一个透明通道。 Scalar Scalar用于表示颜色或像素的值,他是一个模板类,存储了4个值,每个值代表图像的颜色通道,如BGR或HSV。 template<typename _Tp> class Scalar_ { public: _Tp val[4]; // 颜色值,最多支持四个通道 .... }; cv::Scalar(0, 0, 255); // 红色 BGR 颜色,表示 (蓝色 = 0, 绿色 = 0, 红色 = 255) cv::Scalar(255, 0, 0); // 蓝色 BGR 颜色,表示 (蓝色 = 255, 绿色 = 0, 红色 = 0) cv::Scalar(0, 255, 0); // 绿色 BGR 颜色,表示 (蓝色 = 0, 绿色 = 255, 红色 = 0) cv::Scalar(0, 255, 0, 128); // 半透明绿色 cv::Scalar(255, 0, 0, 255); // 完全不透明的蓝色 Size Size类用来表示图像的大小、矩阵的尺寸。 class Size { public: Size(); Size(int _width, int _height); Size(double _width, double _height); int width; // 图像宽度 int height; // 图像高度 }; 下面是常用的用法。 设置图像的尺寸 cv::Mat image = cv::imread("image.jpg"); cv::Size new_size(800, 600); // 新的尺寸 cv::resize(image, image, new_size); // 将图像调整为新尺寸 获取图像的尺寸 cv::Mat image = cv::imread("image.jpg"); cv::Size image_size = image.size(); // 获取图像尺寸 创建一个尺寸大小的矩阵 cv::Mat mat(cv::Size(400, 300), CV_8UC1); // 创建一个大小为 400x300 的单通道图像 cvtColor cvtColor用于颜色空间的转化,图像的颜色空间一般有RGB,BGR(RGB存储顺序的不同),HSV,Lab等,其可以将图像从一种颜色空间变换为另一种颜色空间。 cv::Mat cv::cvtColor(const cv::Mat& src, cv::Mat& dst, int code, int dstCn = 0); src: 输入图像 dst:输出图像 code:指定转化格式,如 cv::COLOR_BGR2GRAY,cv::COLOR_BGR2HSV。 dstCn:输出通道数,如果是0,表示通道数与目标颜色空间通道匹配。 RGB转BGR cv::Mat src = cv::imread("image.jpg"); // 读取 RGB 图像(如果图像是 RGB 格式的话) cv::Mat bgr; cv::cvtColor(src, bgr, cv::COLOR_RGB2BGR); // 转换为 BGR 图像 HSB转BRG cv::Mat hsv = cv::imread("image_hsv.jpg"); // 读取 HSV 图像 cv::Mat bgr; cv::cvtColor(hsv, bgr, cv::COLOR_HSV2BGR); // 转换为 BGR 图像 BRG转灰度 cv::Mat src = cv::imread("image.jpg"); // 读取 BGR 图像 cv::Mat gray; cv::cvtColor(src, gray, cv::COLOR_BGR2GRAY); // 转换为灰度图