note

注意,因为配置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下也是都可以的)。

01. 带滑动轨迹横条GUI的参数修改

#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;
}

02. 提取螺母防松动标记

这个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> &centers, 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;
}

03. 互动获取像素值及分割

interactiveColorDetect.cpp # 放这里做个参考吧,里面sprintf函数会报错,还没去解决

interactiveColorSegment.cpp

04. 获取fps

说明: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方法进行。

#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;
}

05. 鼠标事件的demo

这是图形学中的作业四,画贝塞尔曲线,关于鼠标事件这个作用里用的比较简单明了,可以参考看看。白塞尔曲线实现的来源是看的这里

#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;
}

06. cvui 界面

​ “cvui.hpp”(这个文件在此文档所在路径):是用opencv自己写的带ui界面的操作,It is a C++, header-only and cross-platform。来自于LearnOpenCV,可以根据它里面的教程去看它demo一步步的实现,后面一些简单的交互界面就考虑它了。

07. 多线程读相机

按键退出时还是有点问题,解决不了。

#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, &timestamp);
	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;
}

注意:

opencv的一些api

1. cv::resize

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);

2. 检测点是否在轮廓内

函数应该是:cv::pointPolygonTest

​ 看到群友发言,说是搞成二值图,然后把轮廓内的像素都置为0,这样还有像素为1的点就在轮廓外,是个思路。另外学习opengl的时候,可以用向量的乘积的正负来判断这个问题。

OpenCV是自带这个api的,可看这篇文章

3. cv::glob

​ 类似于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));
}

4. cv::fillPoly

填充指定区域:

#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;
}