注意,因为配置opencv的lib库时没带d,不是debug版本,vs选择debug时,编译不会出错,但是一运行基本就会出错,主要提现在cv::imread()图片后,数据是空的,去cv::imshow()就会报错,可以在属性中去添加这个名字中带d的lib;
有的时候在又是在debug模式下是正常的,在release模式下又报错,大抵就是lib库是debug的,注意库的版本要和模式对应起来,(我同时把opencv的debug和release的lib都加进设置里面先后顺序都试过,只有release是ok的。有的时候又是只有Debug才行,主要针对vs中)(==新的解决办法来了==:配置的时在属性中所有配置(左上角)中统一添加环境变量,头文件路径、库文件路径,然后在release中添加不带d的lib,debug中添加带d的lib,就随便用了)
一定注意:在c++版本中读取rtsp摄像头、视频文件时,一定要下面这么写
cv::VideoCapture cap;
if (argc == 1) {
cap.open(0);
}
else if (argc == 2) {
// 一定要使用open这种方式
cap.open("rtsp://192.168.108.11:554/"); // capo.open(argv[1]);
}
尽量用open()这个方法,确保成功,不要写成cv::VideoCapture cap(rtsp地址),不然vs环境的nmake这种方式就会读取失败,虽然vs的sln里都能正常运行(linux下也是都可以的)。
#include <iostream>
#include <string>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
static void on_ContrastAndBright(int, void *);
int g_nContrastValue = 80; // 对比度
int g_nBrightValue = 80; // 亮度
const std::string src_name = "【原始图窗口】";
const std::string dst_name = "【效果图窗口】";
cv::Mat g_srcImage, g_dstImage;
int main(int argc, char** argv) {
cv::Mat img(300, 300, CV_8UC3, cv::Scalar(0, 255, 255));
g_srcImage = cv::imread("./1.jpg");
if (!g_srcImage.data) {
std::cout << "图片读取错误,检查该路径或是否是Release模式~" << std::endl;
return -1;
}
std::cout << g_srcImage.size() << std::endl; // [700 x 704]
std::cout << g_srcImage.type() << std::endl; // 16
g_dstImage = cv::Mat::zeros(g_srcImage.size(), g_srcImage.type());
// 创建效果图窗口
cv::namedWindow(dst_name, 1);
// 创建轨迹条
cv::createTrackbar("对比度: ", dst_name, &g_nContrastValue, 300, on_ContrastAndBright);
cv::createTrackbar("亮 度: ", dst_name, &g_nBrightValue, 200, on_ContrastAndBright);
// 进行回调函数初始化
on_ContrastAndBright(g_nContrastValue, 0);
on_ContrastAndBright(g_nBrightValue, 0);
// 按下q时,程序退出
while (char(cv::waitKey(1)) != 'q') {}
return 0;
}
static void on_ContrastAndBright(int, void *) {
// 改成0,图像可让拉拽缩放(但可能一开始图像的显示就不那么完全)
cv::namedWindow(src_name, 1);
// 3个for循环,执行运算g_dstImage(i, j) = a*g_srcImage(i,j) + b
for (int y = 0; y < g_srcImage.rows; ++y) {
for (int x = 0; x < g_srcImage.cols; ++x) {
for (int c = 0; c < 3; ++c) {
g_dstImage.at<cv::Vec3b>(y, x)[c] =
cv::saturate_cast<uchar> ((g_nContrastValue*0.01) * (g_srcImage.at<cv::Vec3b>(y, x)[c]) + g_nBrightValue);
// 上面这个函数是用于溢出保护,大致是if(data<0) data=0; else if(data>255) data=255;
}
}
}
// 显示图像
cv::imshow(src_name, g_srcImage);
cv::imshow(dst_name, g_dstImage);
}
// 如果是视频要退出:
// 任意键:if ((cv::waitKey(1) & 0xff) != 255) break; // 注意前面的 & 运算要用括号括起来
// 指定键:if (char(cv::waitKey(1)) == 'q') break;
一个简单的python版本:(相关鼠标事件cv2.setMouseCallback()、滑动条的demo,这个网址点进去下滑到第7点)(pdf书,opencv-python中文教程似乎更不错,关于这个)
name = "image"
image = cv2.imread("35.jpg")
cv2.namedWindow(name, 0)
# 初始阈值给到10,最大到100,后面这个函数我感觉只是占位置的
cv2.createTrackbar("thresh", name, 10, 100, lambda x: None)
while True:
# 主要是这两个函数
num = cv2.getTrackbarPos("thresh", name)
ret, img = cv2.threshold(image, num, 255, cv2.THRESH_BINARY)
cv2.imshow(name, img)
if cv2.waitKey(1) & 0xFF != 255:
break
均值滤波带滑动条
#include <iostream>
#include <string>
#include <opencv2/opencv.hpp>
#define KERNEL_THRESHOLD 200
void blurCallBack(int, void *);
int gKernelSize = 20;
cv::Mat gImgOri, gImgOut;
int main() {
gImgOri = cv::imread("E:\\PycharmProject\\kit_check\\35.jpg");
cv::namedWindow("imgOri", 0);
cv::imshow("imgOri", gImgOri);
blur(gImgOri, gImgOut, cv::Size(gKernelSize, gKernelSize));
cv::namedWindow("imgOut", 0);
cv::imshow("imgOut", gImgOut);
cv::createTrackbar("Kernel Size", "imgOut", &gKernelSize, KERNEL_THRESHOLD, blurCallBack);
cv::waitKey(0);
return true;
}
void blurCallBack(int, void *) {
if (gKernelSize <= 0)
return;
blur(gImgOri, gImgOut, cv::Size(gKernelSize, gKernelSize));
imshow("imgOut", gImgOut);
std::cout << gKernelSize << std::endl;
}
这个vs下,只有Debug才能运行,release又不行,费解。
#include <iostream>
#include <opencv2/opencv.hpp>
#include <vector>
#include <cmath>
#define M_PI 3.14159265358979323846
cv::Mat extract_read_area(cv::Mat &src_img) {
// 提取红色区域部分的二值图
int h = src_img.rows;
int w = src_img.cols;
cv::Mat img_hsv;
cv::cvtColor(src_img, img_hsv, cv::COLOR_BGR2HSV);
cv::Mat out_img = cv::Mat::zeros(h, w, CV_8UC1); // CV_8UC1这个格式,不知为啥一定这
for (int x = 0; x < h; ++x) {
for (int y = 0; y < w; ++y) {
cv::Vec3b pixel = img_hsv.at<cv::Vec3b>(x, y); // [11, 13, 52] 这种格式的
size_t point_h = pixel[0];
size_t point_s = pixel[1];
size_t point_v = pixel[2];
// hsv 红色范围
if ((point_h > 156 && point_h < 180) && (point_s > 43 && point_s < 255) && (point_v > 46 && point_v < 255)) {
// 在clion中下面必须有一个空行或者这阿行后面加一个分号,不然就没结果,我不理解
out_img.at<uchar>(x, y) = 255; // 这里必须得是 uchar类型
}
}
}
return out_img;
}
void find_filter_contours(cv::Mat threshold_img, std::vector<std::vector<cv::Point>> &out_contours) {
std::vector<std::vector<cv::Point>> contours;
std::vector<cv::Vec4i> hierarchy;
// 下面这个函数有两个重载版本,一个版本可以不要hierarchy这个参数
cv::findContours(threshold_img, contours, hierarchy,cv::RETR_TREE, cv::CHAIN_APPROX_SIMPLE);
// 过滤轮廓
std::vector<std::vector<cv::Point>>::iterator iter = contours.begin();
for (; iter != contours.end(); ++iter) {
cv::Rect rect = cv::boundingRect(*iter);
int width = rect.width;
int height = rect.height;
if (width < 10 || height < 10) continue;
out_contours.push_back(*iter);
}
}
void draw_obtain(cv::Mat &out_img ,std::vector<std::vector<cv::Point>> &contours, std::vector<cv::Point2i> ¢ers, std::vector<float> &angles) {
for (auto vec : contours) {
// 把中心点获取
cv::RotatedRect rect = cv::minAreaRect(vec);
cv::Point2f center = rect.center;
int center_x = (int)center.x;
int center_y = (int)center.y;
centers.push_back(cv::Point2i(center_x, center_y));
// 把对应角度获取
float rect_w = rect.size.width;
float rect_h = rect.size.height;
float angle;
if (rect_w > rect_h) {
angle = rect.angle;
}
else {
angle = -(90.0f - rect.angle);
}
angles.push_back(angle);
printf("\ncenter_position: %d %d\n", center_x, center_y);
printf("rect_h_w: %f %f angle: %f°\n", rect_h, rect_w, angle);
// 画出来中心点
cv::circle(out_img, cv::Point2i(center_x, center_y), 5, cv::Scalar(0, 255, 255), -1);
// 画出最小外接矩形
cv::Point2f box[4]; // 最小外接矩形的四个点
rect.points(box);
std::vector<cv::Point> box_int;
for (int i = 0; i < (sizeof(box) / sizeof(box[i])); ++i) {
// 要把坐标从float转成int,同时把它从box数组[]转换成vector
box_int.push_back(cv::Point(std::round(box[i].x), std::round(box[i].y)));
}
// 是画的contours,可能有多个,所以最外层要用vector包一下
cv::drawContours(out_img, std::vector<std::vector<cv::Point>>{box_int}, 0, cv::Scalar(0, 255, 0), 2);
}
}
int main(int argc, char **argv) {
cv::Mat img = cv::imread(R"(51.jpg)"); // 文件夹此笔记所在目录文件夹中
// (1)抽取红色范围的二值图
cv::Mat img_read = extract_read_area(img);
//std::cout << typeid(img_read).name() << std::endl; // class cv::Mat
//std::cout << typeid(img_read.at<cv::Vec2b>(0, 0)[1]).name() << std::endl; // unsigned char
// (2)获取轮廓并进行初步筛选
std::vector<std::vector<cv::Point>> contours;
find_filter_contours(img_read, contours);
// (3)将筛选后的轮廓画出来,并获取到这些轮廓的中心点和其对应的角度
cv::Mat result_img;
std::vector<cv::Point2i> centers; // 图目标们的中心点
std::vector<float> angles; // 对应的角度
img.copyTo(result_img);
draw_obtain(result_img, contours, centers, angles);
// (4)如果是一条完整的连续的线,也就一条;没错位但中间断开了,或者是错位了,就是两个轮廓;还有误判会可能不值2个框
if (centers.size() > 1) {
cv::Point center_1= centers[0], center_2 = centers[1];
float slope = (float)(center_1.y - center_2.y) / (float)(center_1.x - center_2.x);
// slope为正,方向就是对的,因为原点在左上角,slope为负,
float center_angle = 0.0f;
if (slope >= 0) {
center_angle = std::atan(slope) * 180.f / M_PI;
}
else {
center_angle = -1.0f * std::atan(std::abs<float>(slope)) * 180.f / M_PI;
}
std::cout << "两个中心点之间连线形成的角度:" << center_angle << "°" <<std::endl;
if (std::abs(angles[0] - angles[1]) > 5.f ||
std::abs((angles[0] + angles[1]) / 2.f - center_angle) > 10.f) {
std::cout << "防松动标记位置已经改变,请及时检查!\n" << std::endl;
cv::putText(result_img, "Error!", cv::Point(10, 30), cv::FONT_HERSHEY_SIMPLEX, 1.0, cv::Scalar(0, 0, 255), 2);
}
else {
std::cout << "防松动标记位置正常!\n" << std::endl;
cv::putText(result_img, "Normal", cv::Point(10, 30), cv::FONT_HERSHEY_SIMPLEX, 1.0, cv::Scalar(0, 255, 0), 2);
}
}
cv::imshow("1", img);
cv::imshow("2", result_img);
cv::waitKey(0);
cv::destroyAllWindows(); // 摄像头的话:cap.release();
return 0;
}
interactiveColorDetect.cpp # 放这里做个参考吧,里面sprintf函数会报错,还没去解决
-
#include "opencv2/opencv.hpp" #include <iostream> using namespace cv; using namespace std; //Global Variables Mat img, placeholder; // Callback function for any event on he mouse void onMouse( int event, int x, int y, int flags, void* userdata ) { if( event == EVENT_MOUSEMOVE ) { Vec3b bgrPixel(img.at<Vec3b>(y, x)); Mat3b hsv,ycb,lab; // Create Mat object from vector since cvtColor accepts a Mat object Mat3b bgr (bgrPixel); //Convert the single pixel BGR Mat to other formats cvtColor(bgr, ycb, COLOR_BGR2YCrCb); cvtColor(bgr, hsv, COLOR_BGR2HSV); cvtColor(bgr, lab, COLOR_BGR2Lab); //Get back the vector from Mat Vec3b hsvPixel(hsv.at<Vec3b>(0,0)); Vec3b ycbPixel(ycb.at<Vec3b>(0,0)); Vec3b labPixel(lab.at<Vec3b>(0,0)); // Create an empty placeholder for displaying the values placeholder = Mat::zeros(img.rows,400,CV_8UC3); //fill the placeholder with the values of color spaces putText(placeholder, format("BGR [%d, %d, %d]",bgrPixel[0],bgrPixel[1],bgrPixel[2]), Point(20, 70), FONT_HERSHEY_COMPLEX, .9, Scalar(255,255,255), 1); putText(placeholder, format("HSV [%d, %d, %d]",hsvPixel[0],hsvPixel[1],hsvPixel[2]), Point(20, 140), FONT_HERSHEY_COMPLEX, .9, Scalar(255,255,255), 1); putText(placeholder, format("YCrCb [%d, %d, %d]",ycbPixel[0],ycbPixel[1],ycbPixel[2]), Point(20, 210), FONT_HERSHEY_COMPLEX, .9, Scalar(255,255,255), 1); putText(placeholder, format("LAB [%d, %d, %d]",labPixel[0],labPixel[1],labPixel[2]), Point(20, 280), FONT_HERSHEY_COMPLEX, .9, Scalar(255,255,255), 1); Size sz1 = img.size(); Size sz2 = placeholder.size(); //Combine the two results to show side by side in a single image Mat combinedResult(sz1.height, sz1.width+sz2.width, CV_8UC3); Mat left(combinedResult, Rect(0, 0, sz1.width, sz1.height)); img.copyTo(left); Mat right(combinedResult, Rect(sz1.width, 0, sz2.width, sz2.height)); placeholder.copyTo(right); imshow("PRESS P for Previous, N for Next Image", combinedResult); } } int main( int argc, const char** argv ) { // filename // Read the input image int image_number = 0; int nImages = 10; if(argc > 1) nImages = atoi(argv[1]); char filename[20]; sprintf(filename,"images/rub%02d.jpg",image_number%nImages); img = imread(filename); // Resize the image to 400x400 Size rsize(400,400); resize(img,img,rsize); if(img.empty()) { return -1; } // Create an empty window namedWindow("PRESS P for Previous, N for Next Image", WINDOW_AUTOSIZE); // Create a callback function for any event on the mouse setMouseCallback( "PRESS P for Previous, N for Next Image", onMouse ); imshow( "PRESS P for Previous, N for Next Image", img ); while(1) { char k = waitKey(1) & 0xFF; if (k == 27) break; //Check next image in the folder if (k =='n') { image_number++; sprintf(filename,"images/rub%02d.jpg",image_number%nImages); img = imread(filename); resize(img,img,rsize); } //Check previous image in he folder else if (k =='p') { image_number--; sprintf(filename,"images/rub%02d.jpg",image_number%nImages); img = imread(filename); resize(img,img,rsize); } } return 0; }
interactiveColorSegment.cpp
-
#include "opencv2/opencv.hpp" #include <iostream> #include <cstring> using namespace cv; using namespace std; // global variable to keep track of bool show = false; // Create a callback for event on trackbars void onTrackbarActivity(int pos, void* userdata){ // Just uodate the global variable that there is an event show = true; return; } int main(int argc, char **argv) { int image_number = 0; int nImages = 10; if(argc > 1) nImages = atoi(argv[1]); char filename[20]; sprintf(filename,"images/rub%02d.jpg",image_number%nImages); Mat original = imread(filename); // image resize width and height int resizeHeight = 250; int resizeWidth = 250; Size rsize(resizeHeight,resizeWidth); resize(original, original, rsize); // position on the screen where the windows start int initialX = 50; int initialY = 50; // creating windows to display images namedWindow("P-> Previous, N-> Next", WINDOW_AUTOSIZE); namedWindow("SelectBGR", WINDOW_AUTOSIZE); namedWindow("SelectHSV", WINDOW_AUTOSIZE); namedWindow("SelectYCB", WINDOW_AUTOSIZE); namedWindow("SelectLAB", WINDOW_AUTOSIZE); // moving the windows to stack them horizontally moveWindow("P-> Previous, N-> Next", initialX, initialY); moveWindow("SelectBGR", initialX + 1 * (resizeWidth + 5), initialY); moveWindow("SelectHSV", initialX + 2 * (resizeWidth + 5), initialY); moveWindow("SelectYCB", initialX + 3 * (resizeWidth + 5), initialY); moveWindow("SelectLAB", initialX + 4 * (resizeWidth + 5), initialY); // creating trackbars to get values for YCrCb createTrackbar("CrMin", "SelectYCB", 0, 255, onTrackbarActivity); createTrackbar("CrMax", "SelectYCB", 0, 255, onTrackbarActivity); createTrackbar("CbMin", "SelectYCB", 0, 255, onTrackbarActivity); createTrackbar("CbMax", "SelectYCB", 0, 255, onTrackbarActivity); createTrackbar("YMin", "SelectYCB", 0, 255, onTrackbarActivity); createTrackbar("YMax", "SelectYCB", 0, 255, onTrackbarActivity); // creating trackbars to get values for HSV createTrackbar("HMin", "SelectHSV", 0, 180, onTrackbarActivity); createTrackbar("HMax", "SelectHSV", 0, 180, onTrackbarActivity); createTrackbar("SMin", "SelectHSV", 0, 255, onTrackbarActivity); createTrackbar("SMax", "SelectHSV", 0, 255, onTrackbarActivity); createTrackbar("VMin", "SelectHSV", 0, 255, onTrackbarActivity); createTrackbar("VMax", "SelectHSV", 0, 255, onTrackbarActivity); // creating trackbars to get values for BGR createTrackbar("BMin", "SelectBGR", 0, 255, onTrackbarActivity); createTrackbar("BMax", "SelectBGR", 0, 255, onTrackbarActivity); createTrackbar("GMin", "SelectBGR", 0, 255, onTrackbarActivity); createTrackbar("GMax", "SelectBGR", 0, 255, onTrackbarActivity); createTrackbar("RMin", "SelectBGR", 0, 255, onTrackbarActivity); createTrackbar("RMax", "SelectBGR", 0, 255, onTrackbarActivity); // creating trackbars to get values for LAB createTrackbar("LMin", "SelectLAB", 0, 255, onTrackbarActivity); createTrackbar("LMax", "SelectLAB", 0, 255, onTrackbarActivity); createTrackbar("AMin", "SelectLAB", 0, 255, onTrackbarActivity); createTrackbar("AMax", "SelectLAB", 0, 255, onTrackbarActivity); createTrackbar("BMin", "SelectLAB", 0, 255, onTrackbarActivity); createTrackbar("BMax", "SelectLAB", 0, 255, onTrackbarActivity); // show all images initially imshow("SelectHSV", original); imshow("SelectYCB", original); imshow("SelectLAB", original); imshow("SelectBGR", original); // declare local variables int BMin, GMin, RMin; int BMax, GMax, RMax; Scalar minBGR, maxBGR; int HMin, SMin, VMin; int HMax, SMax, VMax; Scalar minHSV, maxHSV; int LMin, aMin, bMin; int LMax, aMax, bMax; Scalar minLab, maxLab; int YMin, CrMin, CbMin; int YMax, CrMax, CbMax; Scalar minYCrCb, maxYCrCb; Mat imageBGR, imageHSV, imageLab, imageYCrCb; Mat maskBGR, maskHSV, maskLab, maskYCrCb; Mat resultBGR, resultHSV, resultLab, resultYCrCb; char k; while (1) { imshow("P-> Previous, N-> Next", original); k = waitKey(1) & 0xFF; //Check next image in the folder if (k =='n') { image_number++; sprintf(filename,"images/rub%02d.jpg",image_number%nImages); original = imread(filename); resize(original,original,rsize); show = true; } //Check previous image in he folder else if (k =='p') { image_number--; sprintf(filename,"images/rub%02d.jpg",image_number%nImages); original = imread(filename); resize(original,original,rsize); show = true; } // Close all windows when 'esc' key is pressed if (k == 27) { break; } if (show) { //If there is any event on the trackbar show = false; // Get values from the BGR trackbar BMin = getTrackbarPos("BMin", "SelectBGR"); GMin = getTrackbarPos("GMin", "SelectBGR"); RMin = getTrackbarPos("RMin", "SelectBGR"); BMax = getTrackbarPos("BMax", "SelectBGR"); GMax = getTrackbarPos("GMax", "SelectBGR"); RMax = getTrackbarPos("RMax", "SelectBGR"); minBGR = Scalar(BMin, GMin, RMin); maxBGR = Scalar(BMax, GMax, RMax); // Get values from the HSV trackbar HMin = getTrackbarPos("HMin", "SelectHSV"); SMin = getTrackbarPos("SMin", "SelectHSV"); VMin = getTrackbarPos("VMin", "SelectHSV"); HMax = getTrackbarPos("HMax", "SelectHSV"); SMax = getTrackbarPos("SMax", "SelectHSV"); VMax = getTrackbarPos("VMax", "SelectHSV"); minHSV = Scalar(HMin, SMin, VMin); maxHSV = Scalar(HMax, SMax, VMax); // Get values from the LAB trackbar LMin = getTrackbarPos("LMin", "SelectLAB"); aMin = getTrackbarPos("AMin", "SelectLAB"); bMin = getTrackbarPos("BMin", "SelectLAB"); LMax = getTrackbarPos("LMax", "SelectLAB"); aMax = getTrackbarPos("AMax", "SelectLAB"); bMax = getTrackbarPos("BMax", "SelectLAB"); minLab = Scalar(LMin, aMin, bMin); maxLab = Scalar(LMax, aMax, bMax); // Get values from the YCrCb trackbar YMin = getTrackbarPos("YMin", "SelectYCB"); CrMin = getTrackbarPos("CrMin", "SelectYCB"); CbMin = getTrackbarPos("CbMin", "SelectYCB"); YMax = getTrackbarPos("YMax", "SelectYCB"); CrMax = getTrackbarPos("CrMax", "SelectYCB"); CbMax = getTrackbarPos("CbMax", "SelectYCB"); minYCrCb = Scalar(YMin, CrMin, CbMin); maxYCrCb = Scalar(YMax, CrMax, CbMax); // Convert the BGR image to other color spaces original.copyTo(imageBGR); cvtColor(original, imageHSV, COLOR_BGR2HSV); cvtColor(original, imageYCrCb, COLOR_BGR2YCrCb); cvtColor(original, imageLab, COLOR_BGR2Lab); // Create the mask using the min and max values obtained from trackbar and apply bitwise and operation to get the results inRange(imageBGR, minBGR, maxBGR, maskBGR); resultBGR = Mat::zeros(original.rows, original.cols, CV_8UC3); bitwise_and(original, original, resultBGR, maskBGR); inRange(imageHSV, minHSV, maxHSV, maskHSV); resultHSV = Mat::zeros(original.rows, original.cols, CV_8UC3); bitwise_and(original, original, resultHSV, maskHSV); inRange(imageYCrCb, minYCrCb, maxYCrCb, maskYCrCb); resultYCrCb = Mat::zeros(original.rows, original.cols, CV_8UC3); bitwise_and(original, original, resultYCrCb, maskYCrCb); inRange(imageLab, minLab, maxLab, maskLab); resultLab = Mat::zeros(original.rows, original.cols, CV_8UC3); bitwise_and(original, original, resultLab, maskLab); // Show the results imshow("SelectBGR", resultBGR); imshow("SelectYCB", resultYCrCb); imshow("SelectLAB", resultLab); imshow("SelectHSV", resultHSV); } } destroyAllWindows(); return 0; }
说明:getTickCount:
它返回从操作系统启动到当前所经过的毫秒数,常常用来判断某个方法执行的时间,其函数原型是DWORD GetTickCount(void),返回值以32位的双字类型DWORD存储,因此可以存储的最大值是2^32 ms约为49.71天,因此若系统运行时间超过49.71天时,这个数就会归0,MSDN中也明确的提到了:"Retrieves the number of milliseconds that have elapsed since the system was started, up to 49.7 days."。因此,如果是编写服务器端程序,此处一定要万分注意,避免引起意外的状况。
特别注意:这个函数并非实时发送,而是由系统每18ms发送一次,因此其最小精度为18ms。当需要有小于18ms的精度计算时,应使用StopWatch方法进行。
- 连续触发200次,实测下来,最小间隔在15ms。
#include <stdio.h> // sprintf 函数需要
// 有的视频可以直接获取fps:
int video_fps = (int)capture.get(cv::CAP_PROP_FPS);
double t = 0.0, fps = 0.0;
char fps_string[10]; // 用于存放帧率的字符串
while (cap.isOpened()) {
// 计算fps,从头到尾
t = (double)cv::getTickCount(); // getTickcount函数:返回从操作系统启动到当前所经过的毫秒数
// cap>>frame; 读取视频要放在 t 后面
// 中间是一系列的计算
t = ((double)cv::getTickCount() - t) / cv::getTickFrequency();
fps = 1.0 / t;
// getTickFrequency函数:返回每秒的计时周期数
// t为该处代码执行所耗的时间,单位为秒,fps为其倒数
sprintf(fps_string, "%.2f", fps); // 帧率保留两位小数
std::string fpsString("fps: ");
fpsString += fps_string;
cv::putText(image, fpsString, cv::Point(10, 30), cv::FONT_HERSHEY_PLAIN, 2, cv::Scalar(0, 0, 255), 2, cv::LINE_AA);
cv::imshow(window_name, image);
int c = cv::waitKey(1);
if ((char)c == 'q') break;
}
这是图形学中的作业四,画贝塞尔曲线,关于鼠标事件这个作用里用的比较简单明了,可以参考看看。白塞尔曲线实现的来源是看的这里。
#include <chrono>
#include <iostream>
#include <opencv2/opencv.hpp>
std::vector<cv::Point2f> control_points;
void mouse_handler(int event, int x, int y, int flags, void *userdata) {
if (event == cv::EVENT_LBUTTONDOWN && control_points.size() < 4) {
std::cout << "Left button of the mouse is clicked - position (" << x << ", "
<< y << ")" << '\n';
control_points.emplace_back(x, y);
}
}
void naive_bezier(const std::vector<cv::Point2f> &points, cv::Mat &window) {
auto &p_0 = points[0];
auto &p_1 = points[1];
auto &p_2 = points[2];
auto &p_3 = points[3];
for (double t = 0.0; t <= 1.0; t += 0.001) {
auto point = std::pow(1 - t, 3) * p_0 + 3 * t * std::pow(1 - t, 2) * p_1 +
3 * std::pow(t, 2) * (1 - t) * p_2 + std::pow(t, 3) * p_3;
window.at<cv::Vec3b>(point.y, point.x)[2] = 255;
}
}
cv::Point2f recursive_bezier(const std::vector<cv::Point2f> &control_points, float t) {
if (control_points.size() == 2)
return control_points[0] + t * (control_points[1] - control_points[0]);
std::vector<cv::Point2f> control_points_temp;
for (int i = 0; i < control_points.size() - 1; i++)
control_points_temp.push_back(control_points[i] + t * (control_points[i + 1] - control_points[i]));
// TODO: Implement de Casteljau's algor
return recursive_bezier(control_points_temp, t);
}
void bezier(const std::vector<cv::Point2f> &control_points, cv::Mat &window) {
// TODO: Iterate through all t = 0 to t = 1 with small steps, and call de Casteljau's
// recursive Bezier algorithm.
for (double t = 0; t <= 1; t += 0.001) {
auto point = recursive_bezier(control_points, t);
window.at<cv::Vec3b>(point.y, point.x)[1] = 255;
}
}
int main() {
cv::Mat window = cv::Mat(700, 700, CV_8UC3, cv::Scalar(0));
cv::cvtColor(window, window, cv::COLOR_BGR2RGB);
cv::namedWindow("Bezier Curve", cv::WINDOW_AUTOSIZE);
cv::setMouseCallback("Bezier Curve", mouse_handler, nullptr);
int key = -1;
while (key != 27) {
for (auto &point : control_points) {
cv::circle(window, point, 3, {255, 255, 255}, 3);
}
if (control_points.size() == 4) {
// 主要是这两行是画曲线,可以只执行其中一个
naive_bezier(control_points, window);
bezier(control_points, window);
cv::imshow("Bezier Curve", window);
cv::imwrite("my_bezier_curve.png", window);
key = cv::waitKey(0);
return 0;
}
cv::imshow("Bezier Curve", window);
key = cv::waitKey(20);
}
return 0;
}
“cvui.hpp”(这个文件在此文档所在路径):是用opencv自己写的带ui界面的操作,It is a C++, header-only and cross-platform。来自于LearnOpenCV,可以根据它里面的教程去看它demo一步步的实现,后面一些简单的交互界面就考虑它了。
-
demo1: 鼠标点击,界面有计数
#include <opencv2/opencv.hpp> #include "cvui.hpp" #define WINDOW_NAME "CVUI Hello World!" int main(int argc, const char *argv[]) { cv::Mat frame = cv::Mat(200, 500, CV_8UC3); int count = 0; // Init a OpenCV window and tell cvui to use it. // If cv::namedWindow() is not used, mouse events will // not be captured by cvui. cv::namedWindow(WINDOW_NAME); cvui::init(WINDOW_NAME); while (true) { // Fill the frame with a nice color frame = cv::Scalar(49, 52, 49); // Buttons will return true if they were clicked, which makes // handling clicks a breeze. if (cvui::button(frame, 110, 80, "Hello, world!")) { // The button was clicked, so let's increment our counter. count++; } // Sometimes you want to show text that is not that simple, e.g. strings + numbers. // You can use cvui::printf for that. It accepts a variable number of parameter, pretty // much like printf does. // Let's show how many times the button has been clicked. cvui::printf(frame, 250, 90, 0.4, 0xff0000, "Button click count: %d", count); // This function must be called *AFTER* all UI components. It does // all the behind the scenes magic to handle mouse clicks, etc. cvui::update(); // Show everything on the screen cv::imshow(WINDOW_NAME, frame); // Check if ESC key was pressed if (cv::waitKey(20) == 27) { break; } } cv::destroyAllWindows(); return 0; }
-
demo2: 勾选框启用canny边缘检测
#include <opencv2/opencv.hpp> #include "cvui.hpp" #define WINDOW_NAME "CVUI Canny Edge" int main(int argc, const char *argv[]) { cv::Mat lena = cv::imread("lena.jpg"); cv::Mat frame = lena.clone(); int low_threshold = 50, high_threshold = 150; bool use_canny = false; // Init a OpenCV window and tell cvui to use it. // If cv::namedWindow() is not used, mouse events will // not be captured by cvui. cv::namedWindow(WINDOW_NAME); cvui::init(WINDOW_NAME); while (true) { // Should we apply Canny edge? if (use_canny) { // Yes, we should apply it. cv::cvtColor(lena, frame, cv::COLOR_BGR2GRAY); cv::Canny(frame, frame, low_threshold, high_threshold, 3); } else { // No, so just copy the original image to the displaying frame. lena.copyTo(frame); } // Render the settings window to house the checkbox // and the trackbars below. cvui::window(frame, 10, 50, 180, 180, "Settings"); // Checkbox to enable/disable the use of Canny edge cvui::checkbox(frame, 15, 80, "Use Canny Edge", &use_canny); // Two trackbars to control the low and high threshold values // for the Canny edge algorithm. cvui::trackbar(frame, 15, 110, 165, &low_threshold, 5, 150); cvui::trackbar(frame, 15, 180, 165, &high_threshold, 80, 300); // This function must be called *AFTER* all UI components. It does // all the behind the scenes magic to handle mouse clicks, etc. cvui::update(); // Show everything on the screen cv::imshow(WINDOW_NAME, frame); // Check if ESC was pressed if (cv::waitKey(30) == 27) { break; } } cv::destroyAllWindows(); return 0; }
按键退出时还是有点问题,解决不了。
#include <queue>
#include <thread>
#include <chrono>
#include <iostream>
#include <string>
#include <memory>
#include <mutex>
#include <opencv2/opencv.hpp>
void func() {
cv::Mat image = cv::imread(R"(C:\Users\Administrator\Pictures\01.png)");
cv::Mat res;
cv::Rect rect(10, 10, 400, 400);
cv::resize(image(rect), res, cv::Size(400, 400));
cv::imshow("1", res);
cv::waitKey(0);
cv::destroyAllWindows();
}
std::mutex mtx; // 加锁保证线程安全
bool gExit = false;
std::string get_timestamp() {
/*std::chrono::milliseconds ms = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch());
std::time_t time = ms.count();
char str[100];
std::strftime(str, sizeof(str), "%Y-%m-%d %H:%M:%S", std::localtime(&time));*/
std::tm time_info{};
std::time_t timestamp = std::time(nullptr);
errno_t err = localtime_s(&time_info, ×tamp);
if (err) {
std::cout << "Failed to convert timestamp to time\n";
return std::string("error");
}
char str[100]{};
std::strftime(str, sizeof(str), "%Y-%m-%d %H:%M:%S", &time_info);
return std::string(str);
}
// 如果传进来的是智能指针,这里的参数也要给智能指针,不能是 std::queue<cv::Mat>* que
void get_image(const char* rtsp_path, std::shared_ptr<std::queue<cv::Mat>> que, int video_stride=15) {
cv::VideoCapture cap;
cap.open(rtsp_path);
while (1) {
if (cap.isOpened()) break;
else {
std::cerr << rtsp_path << " 打开失败,正在重试..." << std::endl;
cap.open(rtsp_path);
}
}
long long int n = 0;
while (1) {
n += 1;
bool success = cap.grab(); // .read() = .grab() followed by .retrieve()
if (!success) {
while (1) {
bool ret = cap.open(rtsp_path);
if (ret) break;
else {
std::cerr << get_timestamp() <<": 摄像头读取失败,30秒后再次尝试..." << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(30));
}
}
continue;
}
if (n % video_stride != 0) continue;
cv::Mat frame;
success = cap.retrieve(frame);
if (!success) {
while (1) {
bool ret = cap.open(rtsp_path);
if (ret) break;
else {
std::cerr << get_timestamp() << ": 摄像头读取失败,30秒后再次尝试..." << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(30));
}
}
continue;
}
// 必须加锁保证线程安全
std::unique_lock<std::mutex> lock(mtx);
if (que->size() >= 2)
que->pop();
que->push(frame);
lock.unlock();
std::cout << "现在的size: " << que->size() << std::endl;
if (gExit) break;
}
cap.release();
std::cout << "子线程退出" << std::endl;
}
int main(int argc, char** argv) {
const char* video_path = "rtsp://192.168.108.131:554/user=admin&password=&channel=1&stream=0.sdp?";
cv::namedWindow("hello");
// 图像队列
std::shared_ptr<std::queue<cv::Mat>> que = std::make_shared<std::queue<cv::Mat>>();
// 下面不管哪种方式,函数哪怕有默认参数,也一定要给默认参数的值,不然是找不到对应函数的。
/*方式一:使用 bind 绑定
auto f = std::bind(get_image, video_path, que, 15);
std::thread img_thread(f);
*/
/*方式二:使用lambda
std::thread img_thread([video_path, que]() {get_image(video_path, que, 1); }); // 值捕获
std::thread img_thread([&video_path, &que]() {get_image(video_path, que, 1); }); // 引用捕获,
*/
std::thread img_thread([&]() {get_image(video_path, que, 1); }); // 这也是引用捕获
// 这里创建线程就会直接执行了。
while (true) {
if (que && !que->empty()) {
std::cout << "123" << std::endl;
cv::Mat frame;
// 必须加锁保证线程安全
std::unique_lock<std::mutex> lock(mtx);
frame = que->front();
que->pop();
lock.unlock();
// 模拟检测用时
std::this_thread::sleep_for(std::chrono::milliseconds(200));
cv::imshow("hello", frame);
if ((cv::waitKey(1) & 0xFF) != 255) {
gExit = true;
break;
}
}
else {
std::cout << get_timestamp() << "pause" << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
}
std::this_thread::sleep_for(std::chrono::seconds(1));
cv::destroyAllWindows();
return 0;
}
注意:
- 使用智能指针,那函数的参数对应的类型也要是智能指针的类型。
- 为了保证线程安全,操作共享数据时一定要加锁,不然跑一会儿后就会错。
- c++的多线程在创建后就会直接执行,不需要像python那样去.start()。若是用了 .join() 主线程会直接卡在join那一句,直到子线程运行结束。
- 创建c++子线程时,是不能传递参数的,所有得将其包装成可调用对象,如std::bind、lambda。且一定记得函数哪怕有了默认参数,都一定要给。
int h = 480, w = 640;
cv::Mat picture(h, w, CV_8UC3);
cv::resize(img, picture, picture.size(), 0, 0, cv::INTER_LINEAR);
// 其他不重要的,主要是第三个参数,用的 ".size()" 这样才不会报错
// 480 x 640 struct cv::MatSize 这是打印结果。
std::cout << frame.size << typeid(frame.size).name() << std::endl;
// [640 x 480]class cv::Size_<int>
std::cout << frame.size() << typeid(frame.size()).name() << "\n" << std::endl;
同样可以:
cv::resize(img, img, cv::Size(kInputW, kInputH)); // 这样就是直接改变的本身
还可以用它来获得一个图片的部分信息,这样就不用一个个去循环赋值了:
// 这是 yolov5的tensorrt的图片后处理代码中的一个函数
cv::Mat scale_mask(cv::Mat mask, cv::Mat img) {
int x, y, w, h;
float r_w = kInputW / (img.cols * 1.0);
float r_h = kInputH / (img.rows * 1.0);
if (r_h > r_w) {
w = kInputW;
h = r_w * img.rows;
x = 0;
y = (kInputH - h) / 2;
}
else {
w = r_h * img.cols;
h = kInputH;
x = (kInputW - w) / 2;
y = 0;
}
// 主要就是注意下面这几行代码,,resize把整个src初始化成了 dst
cv::Rect rect(x, y, w, h);
cv::Mat res;
// 注意这 mask(rect),源码是重载了 (),这样就是截取了这个区域
cv::resize(mask(rect), res, img.size());
return res;
}
// 截取某个区域额更简单的写法
cv::Mat image = cv::imread(R"(C:\Users\Administrator\Pictures\01.png)");
cv::Rect rect(10, 10, 400, 400);
cv::imshow("1", image(rect)); // 注意这种写法
cv::Mat res;
cv::resize(image(rect), res, cv::Size(400, 400)); // 这样给到 res
// 或者直接
cv::Mat res = image(rect);
函数应该是:cv::pointPolygonTest
看到群友发言,说是搞成二值图,然后把轮廓内的像素都置为0,这样还有像素为1的点就在轮廓外,是个思路。另外学习opengl的时候,可以用向量的乘积的正负来判断这个问题。
OpenCV是自带这个api的,可看这篇文章。
类似于python(import glob; img_path = glob.glob("./*.png"))
#include <string>
#include <opencv2/opencv.hpp>
#include <vector>
int main() {
std::vector< std::string> images_path;
// 这images_path是引用传入,得到的就是前面路径下的所有文件绝对路径
cv::glob("./under/images", images_path);
// 第一张图
cv::Mat img = cv::imread(images_path.at(0));
}
填充指定区域:
#include <vector>
#include <opencv2/opencv.hpp>
int main() {
// 注意:一般opencv中的array,这里都用vector去实现
std::vector<cv::Point> points = { {60, 60}, {40, 10}, {100, 100}, {200, 60}, {300, 50} };
cv::Mat img = cv::Mat::ones(cv::Size(640, 640), CV_8UC3);
cv::rectangle(img, cv::Point(10, 10), cv::Point(60, 60), cv::Scalar(0, 0, 255), 2);
cv::fillPoly(img, points, cv::Scalar(0, 0, 255));
cv::imshow("123", img);
cv::waitKey(0);
}
简单的读取摄像头
#include <iostream>
#include <string>
#include <opencv2/opencv.hpp>
int main(int argc, char** argv) {
const char* video_path = "rtsp://192.168.108.134:554/user=admin&password=&channel=1&stream=1.sdp?";
cv::namedWindow("hello");
cv::VideoCapture cap;
cap.open(video_path);
cv::Mat frame;
while (cap.isOpened()) {
bool ret = cap.read(frame);
if (!ret) break;
cap >> frame;
std::cout << frame.size << typeid(frame.size).name() << std::endl;
std::cout << frame.size() << typeid(frame.size()).name() << "\n" << std::endl;
cv::imshow("hello", frame);
if ((cv::waitKey(1) & 0xFF) != 255) break;
}
cv::destroyAllWindows();
cap.release();
return 0;
}