文章目录
- 前言
- [一、opencv (C++)图片基本操作](#一、opencv (C++)图片基本操作)
-
- [1.1 读取图片并显示](#1.1 读取图片并显示)
- [1.2 颜色转换](#1.2 颜色转换)
- [1.3 图像filtering](#1.3 图像filtering)
- [1.4 形状调整](#1.4 形状调整)
- [1.5 绘制](#1.5 绘制)
- 二、读取视频文件并显示
- [三、RTSP 视频流](#三、RTSP 视频流)
- [四. 人脸检测](#四. 人脸检测)
- 总结
前言
学习笔记
一、opencv (C++)图片基本操作
1.1 读取图片并显示
cpp
#include "opencv2/opencv.hpp"
#include <iostream>
int main(int argc,char** argv)
{
//读取图片,mat是matrix的缩写,是一个矩阵
cv::Mat image = cv::imread("./media/cat.jpg");
//判断是否读取成功
if (image.empty())
{
std::cout<<"Could not read the image:" <<std::endl;
return 1;
}
//打印图片高度和宽度
std::cout<<"height"<<image.rows<<"width"<<image.cols<<std::endl;
//用numpy格式展示图片
std::cout<<"data"<<cv::format(image,cv::Formatter::FMT_NUMPY)<<std::endl;
//用python 列表格式展示图片
std::cout<<"data"<<cv::format(image,cv::Formatter::FMT_PYTHON)<<std::endl;
cv::Mat gray;
cv::cvtColor(image,gray,cv::COLOR_BGR2GRAY);
cv::imshow("image",image);
cv::waitKey(0);
cv::imwrite("./output/gray.jpg",gray);
}
必须在vscode里面展示,不能在服务器上,服务器上没有窗口,或者保存成图片文件查看
1.2 颜色转换
python
#include "opencv2/opencv.hpp"
#include <iostream>
int main()
{
cv::Mat src = cv::imread("./media/dog.jpg");
cv::Mat gray;
cv::cvtColor(src,gray,cv::COLOR_BGR2GRAY);
cv::Mat hsv;
cv::cvtColor(src,hsv,cv::COLOR_BGR2HSV);
cv::Mat rgb;
cv::cvtColor(src,rgb,cv::COLOR_BGR2RGB);
cv::imwrite("./output/1.gray.jpg",gray);
cv::imwrite("./output/1.hsv.jpg",hsv);
cv::imwrite("./output/1.rgb.jpg",rgb);
return 0;
}
1.3 图像filtering
cpp
#include <iostream>
#include <opencv2/opencv.hpp>
int main()
{
cv::Mat src = cv::imread("./media/dog.jpg");
//模糊
cv::Mat blur;
// 三个参数分别是输入图像、输出图像、卷积核大小
cv::GaussianBlur(src,blur,cv::Size(7,7),0);
// 膨胀
cv::Mat dilate;
cv::dilate(src,dilate,cv::getStructuringElement(cv::MORPH_RECT,cv::Size(5,5)));
//腐蚀
cv::Mat erode;
cv::erode(src,erode,cv::getStructuringElement(cv::MORPH_RECT,cv::Size(5,5)));
cv::imwrite("./output/blur.jpg",blur);
cv::imwrite("./output/dilate.jpg",dilate);
cv::imwrite("./output/erode.jpg",erode);
return 0;
}
1.4 形状调整
cpp
// 形状调整
#include "opencv2/opencv.hpp"
#include <iostream>
#include <vector>
int main()
{
// 读取图像
cv::Mat cat_img = cv::imread("./media/cat.jpg");
// ======== resize ========
cv::Mat cat_resize;
// 三个参数分别是输入图像、输出图像、输出图像大小
cv::resize(cat_img, cat_resize, cv::Size(320, 240));
// 保存
cv::imwrite("./output/3.cat_resize.jpg", cat_resize);
// ======== copy ========
cv::Mat copy;
cat_img.copyTo(copy);
cv::imwrite("./output/3.copy.jpg", copy);
// ======== ROI裁剪 ========
cv::Rect rect(100, 100, 200, 100); // x, y, width, height
cv::Mat roi = cat_img(rect);
cv::imwrite("./output/3.roi.jpg", roi);
// ======== 拼接 ========
cv::Mat dog_img = cv::imread("./media/dog.jpg");
cv::Mat dog_resize;
cv::resize(dog_img, dog_resize, cv::Size(320, 240));
// 水平拼接,需要保证两张图片的高度(rows)一致
cv::Mat hconcat_img;
cv::hconcat(cat_resize, dog_resize, hconcat_img);
cv::imwrite("./output/3.hconcat.jpg", hconcat_img);
// 或者使用vector方式
std::vector<cv::Mat> imgs{cat_resize, dog_resize, cat_resize, dog_resize};
cv::Mat hconcat_img2;
cv::hconcat(imgs, hconcat_img2);
cv::imwrite("./output/3.hconcat2.jpg", hconcat_img2);
// 数组方式
cv::Mat imgs_arr[] = {dog_resize, cat_resize, dog_resize, cat_resize};
cv::Mat hconcat_img3;
cv::hconcat(imgs_arr, 4, hconcat_img3); // 4是数组长度
cv::imwrite("./output/3.hconcat3.jpg", hconcat_img3);
// 垂直拼接,需要保证两张图片的宽度(cols)一致
cv::Mat vconcat_img;
cv::vconcat(cat_resize, dog_resize, vconcat_img);
cv::imwrite("./output/3.vconcat.jpg", vconcat_img);
// ======== 翻转 ========
cv::Mat flip;
// 三个参数分别是输入图像、输出图像、翻转方向
cv::flip(cat_img, flip, 1); // 1表示水平翻转,0表示垂直翻转,-1表示水平垂直翻转
cv::imwrite("./output/3.flip.jpg", flip);
// ======== 旋转 ========
cv::Mat rotate;
// 三个参数分别是输入图像、输出图像、旋转角度
cv::rotate(cat_img, rotate, cv::ROTATE_90_CLOCKWISE); // 顺时针旋转90度
cv::imwrite("./output/3.rotate.jpg", rotate);
return 0;
}
1.5 绘制
cpp
// 绘制文字和图形
#include "opencv2/opencv.hpp"
#include <iostream>
#include <vector>
int main()
{
// 创建一个黑色图像,参数分别是图像大小、图像类型,CV_8UC3表示8位无符号整数,3通道
cv::Mat image = cv::Mat::zeros(cv::Size(600, 600), CV_8UC3);
// 绘制直线,参数分别是图像、起点、终点、颜色、线宽、线型
cv::line(image, cv::Point(50, 50), cv::Point(350, 250), cv::Scalar(0, 0, 255), 2, cv::LINE_AA);
// 绘制矩形,参数分别是图像、左上角、右下角、颜色、线宽、线型
cv::rectangle(image, cv::Point(50, 50), cv::Point(350, 250), cv::Scalar(0, 255, 0), 2, cv::LINE_AA);
// 绘制圆形,参数分别是图像、圆心、半径、颜色、线宽、线型
cv::circle(image, cv::Point(200, 150), 100, cv::Scalar(255, 0, 0), 2, cv::LINE_AA);
// 实心
cv::circle(image, cv::Point(200, 150), 50, cv::Scalar(255, 0, 0), -1, cv::LINE_AA);
// // ================== 多边形 ==================
// cv::Point points[2][4]; // 定义两个多边形的顶点数组
// // 第一个多边形的顶点
// points[0][0] = cv::Point(100, 115);
// points[0][1] = cv::Point(255, 135);
// points[0][2] = cv::Point(140, 365);
// points[0][3] = cv::Point(100, 300);
// // 第二个多边形的顶点
// points[1][0] = cv::Point(300, 315);
// points[1][1] = cv::Point(555, 335);
// points[1][2] = cv::Point(340, 565);
// points[1][3] = cv::Point(300, 500);
// // ppt[] 要同时添加两个多边形顶点数组的地址)
// const cv::Point *pts_v[] = {points[0], points[1]};
// // npts_v[]要定义每个多边形的顶点数
// int npts_v[] = {4, 4};
// // 绘制多边形,参数分别是图像、顶点数组、顶点数、曲线数量、是否闭合、颜色、线宽、线型
// cv::polylines(image, pts_v, npts_v, 2, true, cv::Scalar(255, 0, 255), 2, 8, 0);
// ================== 使用vector绘制多边形 ==================
std::vector<cv::Point> points_v;
// 随机生成5个点
for (int i = 0; i < 5; i++)
{
points_v.push_back(cv::Point(rand() % 600, rand() % 600));
}
// 绘制多边形,参数分别是图像、顶点容器、是否闭合、颜色、线宽、线型
cv::polylines(image, points_v, true, cv::Scalar(255, 0, 0), 2, 8, 0);
// ================== 绘制文字 ==================
// 参数分别是图像、文字、文字位置、字体、字体大小、颜色、线宽、线型
cv::putText(image, "Hello World!", cv::Point(400, 50), cv::FONT_HERSHEY_SIMPLEX, 1.0, cv::Scalar(255, 255, 255), 2, 8, 0);
// 保存
cv::imwrite("./output/4.drawing.jpg", image);
return 0;
}
二、读取视频文件并显示
cpp
// 导入opencv 库
#include <opencv2/opencv.hpp>
#include <iostream>
// 导入gflags 库
#include <gflags/gflags.h>
// 定义命令行参数
DEFINE_string(video, "./media/dog.mp4", "Input video"); // 视频路径
int main(int argc, char **argv)
{
// 解析命令行参数
gflags::ParseCommandLineFlags(&argc, &argv, true);
// 读取视频:创建了一个VideoCapture对象,参数为视频路径
cv::VideoCapture capture(FLAGS_video);
// 判断视频是否读取成功,返回true表示成功
if (!capture.isOpened())
{
std::cout << "无法读取视频: " << FLAGS_video << std::endl;
return 1;
}
// 读取视频帧,使用Mat类型的frame存储返回的帧
cv::Mat frame;
// 灰度图
cv::Mat gray_frame;
// 循环读取视频帧
while (true)
{
// 读取视频帧,使用 >> 运算符或者read()函数,他的参数是返回的帧
capture.read(frame);
// capture >> frame;
// 判断是否读取成功
if (frame.empty())
{
std::cout << "文件读取完毕" << std::endl;
break;
}
// 转成灰度图
cv::cvtColor(frame, gray_frame, cv::COLOR_BGR2GRAY);
// 显示视频帧
cv::imshow("raw frame", frame);
cv::imshow("gray frame", gray_frame);
// 等待按键,延迟30ms,否则视频播放太快
int k = cv::waitKey(30);
// 按下ESC键退出
if (k == 27)
{
std::cout << "退出" << std::endl;
break;
}
}
return 0;
}
读取摄像头并写入文件
cpp
// 导入opencv 库
#include <opencv2/opencv.hpp>
#include <iostream>
// 导入gflags 库
#include <gflags/gflags.h>
// 定义命令行参数
DEFINE_int32(camera, 0, "Input camera"); // 摄像头编号
int main(int argc, char **argv)
{
// 解析命令行参数
gflags::ParseCommandLineFlags(&argc, &argv, true);
// 读取视频:创建了一个VideoCapture对象,参数为摄像头编号
cv::VideoCapture capture(FLAGS_camera);
// 设置指定摄像头的分辨率
int width = 640;
int height = 480;
// 设置摄像头宽度和高度
capture.set(cv::CAP_PROP_FRAME_WIDTH, width);
capture.set(cv::CAP_PROP_FRAME_HEIGHT, height);
// 判断视频是否读取成功,返回true表示成功
if (!capture.isOpened())
{
std::cout << "无法打开摄像头: " << FLAGS_camera << std::endl;
return 1;
}
// 读取视频帧,使用Mat类型的frame存储返回的帧
cv::Mat frame;
// 写入MP4文件,参数分别是:文件名,编码格式,帧率,帧大小
cv::VideoWriter writer("./output/record.mp4", cv::VideoWriter::fourcc('H', '2', '6', '4'), 20, cv::Size(width, height));
// 循环读取视频帧
while (true)
{
// 读取视频帧,使用 >> 运算符或者read()函数,他的参数是返回的帧
capture.read(frame);
// capture >> frame;
// flip
cv::flip(frame, frame, 1);
// 显示视频帧
cv::imshow("opencv demo", frame);
// 写入视频
writer.write(frame);
// 等待按键,延迟30ms,否则视频播放太快
int k = cv::waitKey(30);
// 按下ESC键退出
if (k == 27)
{
std::cout << "退出" << std::endl;
break;
}
}
return 0;
}
三、RTSP 视频流
本机构造RTSP视频流(optional)
cpp
# Ubuntu安装ffmpeg
sudo apt-get install ffmpeg
# 赋予权限
chmod +x rtsp-simple-server
chmod +x start_server.sh
# 运行服务
./start_server.sh
# 退出服务
pkill rtsp-simple-server
pkill ffmpeg
用于启动多个视频流服务器和ffmpeg流媒体转发器。它的作用是将四个mp4视频文件以rtsp协议和tcp传输方式推送到本地的rtsp服务器上。同时,还启动了ffserver服务器来接收来自ffmpeg的流媒体流,并将其转发到指定的端口上。
start_server.sh
cpp
#ffserver -f server1.conf &
#ffserver -f server2.conf &
#ffserver -f server3.conf &
#ffserver -f server4.conf &
#ffmpeg -i 1.mp4 -vcodec libx264 -tune zerolatency -crf 18 http://localhost:1234/feed1.ffm &
#ffmpeg -i 2.mp4 -vcodec libx264 -tune zerolatency -crf 18 http://localhost:1235/feed2.ffm &
#ffmpeg -i 3.mp4 -vcodec libx264 -tune zerolatency -crf 18 http://localhost:1236/feed3.ffm &
#ffmpeg -i 4.mp4 -vcodec libx264 -tune zerolatency -crf 18 http://localhost:1237/feed4.ffm &
./rtsp-simple-server rtsp_server.yml &
ffmpeg -re -stream_loop -1 -i 1.mp4 -vcodec copy -acodec copy -f rtsp -rtsp_transport tcp rtsp://localhost:8554/live1.sdp &
ffmpeg -re -stream_loop -1 -i 2.mp4 -vcodec copy -acodec copy -f rtsp -rtsp_transport tcp rtsp://localhost:8554/live2.sdp &
ffmpeg -re -stream_loop -1 -i 3.mp4 -vcodec copy -acodec copy -f rtsp -rtsp_transport tcp rtsp://localhost:8554/live3.sdp &
ffmpeg -re -stream_loop -1 -i 4.mp4 -vcodec copy -acodec copy -f rtsp -rtsp_transport tcp rtsp://localhost:8554/live4.sdp &
使用OpenCV库来读取和显示四个RTSP视频流,并将它们拼接成一个单一的窗口
cpp
#include <opencv2/opencv.hpp>
#include <iostream>
#include <string>
int main(int argc, char **argv)
{
// Ubuntu安装ffmpeg:sudo apt-get install ffmpeg
// rtsp地址变量
// 一般main 主码流,sub 子码流
std::string rtsp1 = "rtsp://admin:SEGJKL@192.168.1.185:554/h264/ch1/sub/av_stream";
std::string rtsp2 = rtsp1;
std::string rtsp3 = rtsp1;
std::string rtsp4 = rtsp1;
// std::string rtsp1 = "rtsp://localhost:8554/live1.sdp";
// std::string rtsp2 = "rtsp://localhost:8554/live2.sdp";
// std::string rtsp3 = "rtsp://localhost:8554/live3.sdp";
// std::string rtsp4 = "rtsp://localhost:8554/live4.sdp";
// CAP_FFMPEG:使用ffmpeg解码
cv::VideoCapture stream1 = cv::VideoCapture(rtsp1, cv::CAP_FFMPEG);
cv::VideoCapture stream2 = cv::VideoCapture(rtsp2, cv::CAP_FFMPEG);
cv::VideoCapture stream3 = cv::VideoCapture(rtsp3, cv::CAP_FFMPEG);
cv::VideoCapture stream4 = cv::VideoCapture(rtsp4, cv::CAP_FFMPEG);
if (!stream1.isOpened() || !stream2.isOpened() || !stream3.isOpened() || !stream4.isOpened())
{
std::cout << "有视频流未打开" << std::endl;
return -1;
}
cv::Mat frame1;
cv::Mat frame2;
cv::Mat frame3;
cv::Mat frame4;
cv::Mat H1, H2, V;
// 使用namedWindow创建窗口,WINDOW_AUTOSIZE:自动调整窗口大小
cv::namedWindow("rtsp_demo", cv::WINDOW_AUTOSIZE);
while (true)
{
if (!stream1.read(frame1) || !stream2.read(frame2) || !stream3.read(frame3) || !stream4.read(frame4))
{
std::cout << "有视频流未读取" << std::endl;
continue;
}
// 缩放等处理
cv::resize(frame1, frame1, cv::Size(500, 300));
cv::resize(frame2, frame2, cv::Size(500, 300));
cv::flip(frame2, frame2, 1);
cv::resize(frame3, frame3, cv::Size(500, 300));
cv::cvtColor(frame1, frame1, cv::COLOR_BGR2GRAY);
cv::cvtColor(frame1, frame1, cv::COLOR_GRAY2BGR);
cv::resize(frame4, frame4, cv::Size(500, 300));
cv::putText(frame4, "RTSP demo", cv::Point(100, 100), cv::FONT_ITALIC, 1, cv::Scalar(0, 0, 255), 2);
// 拼接
cv::hconcat(frame1, frame2, H1);
cv::hconcat(frame3, frame4, H2);
cv::vconcat(H1, H2, V);
cv::imshow("rtsp_demo", V);
if (cv::waitKey(1) == 27)
{
break;
}
}
return 0;
}
四. 人脸检测
cpp
#include "opencv2/opencv.hpp"
#include <iostream>
// 初始化模型
const std::string tensorflowConfigFile = "./weights/opencv_face_detector.pbtxt";
const std::string tensorflowWeightFile = "./weights/opencv_face_detector_uint8.pb";
cv::dnn::Net net = cv::dnn::readNetFromTensorflow(tensorflowWeightFile, tensorflowConfigFile);
// 检测并绘制矩形框
void detectDrawRect(cv::Mat &frame)
{
// 获取图像的宽高
int frameHeight = frame.rows;
int frameWidth = frame.cols;
// 预处理,resize + swapRB + mean + scale
cv::Mat inputBlob = cv::dnn::blobFromImage(frame, 1.0, cv::Size(300, 300), cv::Scalar(104.0, 177.0, 123.0), false, false);
// 推理
net.setInput(inputBlob, "data");
cv::Mat detection = net.forward("detection_out");
// 获取结果
cv::Mat detectionMat(detection.size[2], detection.size[3], CV_32F, detection.ptr<float>());
// 遍历多个人脸结果
for (int i = 0; i < detectionMat.rows; i++)
{
// 置信度
float confidence = detectionMat.at<float>(i, 2);
if (confidence > 0.2)
{
// 两点坐标
int l = static_cast<int>(detectionMat.at<float>(i, 3) * frameWidth);
int t = static_cast<int>(detectionMat.at<float>(i, 4) * frameHeight);
int r = static_cast<int>(detectionMat.at<float>(i, 5) * frameWidth);
int b = static_cast<int>(detectionMat.at<float>(i, 6) * frameHeight);
// 画框
cv::rectangle(frame, cv::Point(l, t), cv::Point(r, b), cv::Scalar(0, 255, 0), 2);
}
}
}
// 图片测试
void imageTest()
{
// 读取图片
cv::Mat img = cv::imread("./media/test_face.jpg");
// 推理
detectDrawRect(img);
// 显示
cv::imshow("image test", img);
// 保存
cv::imwrite("./output/test_face_result.jpg", img);
cv::waitKey(0);
}
// 实时视频流检测
void videoTest()
{
// =========== 摄像头 ===========
// 先读取camera或文件视频流并显示
// cv::VideoCapture cap(2);
// // 设置指定摄像头的分辨率
// int width = 640;
// int height = 480;
// // 设置摄像头宽度和高度
// cap.set(cv::CAP_PROP_FRAME_WIDTH, width);
// cap.set(cv::CAP_PROP_FRAME_HEIGHT, height);
// =========== 文件 ===========
// 先读取camera或文件视频流并显示
cv::VideoCapture cap("./media/video.mp4");
// 获取视频流的宽高
int width = cap.get(cv::CAP_PROP_FRAME_WIDTH);
int height = cap.get(cv::CAP_PROP_FRAME_HEIGHT);
// 构造写入器
// 写入MP4文件,参数分别是:文件名,编码格式,帧率,帧大小
cv::VideoWriter writer("./output/record.mp4", cv::VideoWriter::fourcc('H', '2', '6', '4'), 25, cv::Size(width, height));
if (!cap.isOpened())
{
std::cout << "Cannot open the video cam" << std::endl;
// 退出
exit(1);
}
cv::Mat frame;
while (true)
{
if (!cap.read(frame))
{
std::cout << "Cannot read a frame from video stream" << std::endl;
break;
}
// flip
cv::flip(frame, frame, 1);
// 推理
detectDrawRect(frame);
// 写入
writer.write(frame);
// cv::imshow("MyVideo", frame);
// if (cv::waitKey(1) == 27)
// {
// std::cout << "esc key is pressed by user" << std::endl;
// break;
// }
}
}
int main(int argc, char **argv)
{
// 图片测试
// imageTest();
// 视频测试
videoTest();
return 0;
}
总结
opencv (C++)简单用法