发布时间:2024-02-22 19:00
OpenCV是用C和C++编写的开源计算机视觉库,可以在Windows、linux、Mac OS等系统上运行。同时它提供了Python、Java、Matlab等其他一些语言的接口,将库导入安卓和iOS中为移动设备开发应用。
OpenCV的一个目标是提供易于使用的计算机视觉接口,帮助人们快速建立精巧的视觉应用。OpenCV库包含从计算机视觉各个领域衍生出来的500多个函数,包括工业产品质量检验、医学图像处理、安保领域、交互操作、相机校正、双目视觉以及机器人学。因为计算机视觉经常和机器学习一起使用,所以OpenCV也包含一个完备的,具有通用性的机器学习库(ML模块)。
OpenCV的开源许可允许任何人利用OpenCV包含的任何组件构建商业产品。
在OpenCV的官方网站可以下载最新的且完整的源码及大部分release版本源码。但是我发现OpenCV的官网打不开,这时我们可以在github(https://github.com/opencv/opencv/releases)上下载exe文件。下载后打开安装很快就完成了。注意安装的时候记住安装路径。安装后在文件管理器打开它的安装文件。应该是如下图内容。
接下来就需要配置visual studio了。我用的是vs2019,不同版本的vs可能具体配置也不同,vs2017的用户可以参考https://blog.csdn.net/qq_41498261/article/details/83822094?depth_1-utm_source=distribute.pc_relevant.none-task&utm_source=distribute.pc_relevant.none-task来完成配置。
流程:
视图->属性管理器-> Debug|x64,右击添加新项目属性表,位置最好选择一个好找的位置,名字可以命名为 opencv.props。
#include
#include
int main()
{
cv::Mat img = cv::imread("Hakurei Reimu.png");//图片路径
if (img.empty())return -1;
cv::namedWindow("test", cv::WINDOW_AUTOSIZE);
cv::imshow("test", img);
cv::waitKey(0);
cv::destroyWindow("test");
return 0;
}
注意图片路径要是一个有效的路径,为了避免出错可以将你的一个本地图片复制粘贴到cpp源文件同目录下,这样你就可以直接输入文件名了。最后运行成功的话会出来一个窗口显示相应的图片。
属性表保存好了以后,再新建控制台应用只需要在属性管理器Debug|x64右键选择添加现有属性表添加前面新建的属性表就完事了。
我们在来看一下前面的哪个测试程序
#include
#include
int main()
{
cv::Mat img = cv::imread("Hakurei Reimu.png");//图片路径
if (img.empty())return -1;
cv::namedWindow("test", cv::WINDOW_AUTOSIZE);
cv::imshow("test", img);
cv::waitKey(0);
cv::destroyWindow("test");
return 0;
}
首先OpenCV的函数都位于命名空间cv下,如果想省略cv::,可以在前面用using namespace cv。但这样存在与其他命名空间冲突的风险。
函数imread()根据文件名来决定图像格式的处理,也会自动申请图像需要的内存,它可以读取很多种图像格式,函数返回一个Mat结构,这个结构是OpenCV中你会接触的最多的自带结构,OpenCV用这个结构来处理所有类型的图像:单通道、多通道、整型、浮点数以及各种类型。
函数namedWindow由HighGUi模块提供,将一个名称赋予窗口。第二个参数说明了Windows的特性。默认情况下全部设置为0,也可以设置为WINDOW_AUTOSIZE,这样窗口的大小会和载入图像的大小一致,图像会被缩放以使用窗口。
通过imshow()来进行显示。imshow()将建一个窗口。(如果窗口不存在他会自动调用namedWindow()新建一个窗口)。调用imshow()时窗口将被重绘上要求的图片,并且窗口会自动调整大小(使用参数WINDOW_AUTOSIZE)。
waitkey(0)函数告诉系统暂停并等待键盘事件。如果传入参数大于0则会意味着程序等待的毫秒数,比如waitkey(100)就是等待100毫秒,如果100毫秒没有键盘事件就会继续执行程序。0或负数表示等待无限长时间。
最后用函数destroywindow()来让窗口自行销毁。
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"
using namespace cv;
int main(int argc, char** argv) {
namedWindow("test", WINDOW_AUTOSIZE);
VideoCapture cap;
cap.open("灵梦.mp4");
Mat frame;
for (;;) {
cap >> frame;
if (frame.empty())break;
imshow("test", frame);
if (waitKey(33) >= 0)break;
}
return 0;
}
由于上面的程序无法在视频中进行跳转,所以我们需要添加一个滑动条来实现视频跳转。
下面就是一个稍微复杂的程序
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include
#include
using namespace cv;
using namespace std;
int g_slider_position = 0;
int g_run = 1, g_dontset = 0;
VideoCapture g_cap;
void onTrackbarSlide(int pos, void*) {
g_cap.set(CAP_PROP_POS_FRAMES, pos);
if (!g_dontset)
g_run = 1;
g_dontset = 0;
}
int main(int argc, char** argv) {
namedWindow("test", WINDOW_AUTOSIZE);
g_cap.open("灵梦.mp4");
int frames = (int)g_cap.get(CAP_PROP_FRAME_COUNT);
int tmpw = (int)g_cap.get(CAP_PROP_FRAME_WIDTH);
int tmph = (int)g_cap.get(CAP_PROP_FRAME_HEIGHT);
cout << "Video has " << frames << " frames of dimension (" << tmpw << " , " << tmph << " )." << endl;
createTrackbar("Position", "test", &g_slider_position, frames, onTrackbarSlide);
//不难发现传入的参数分别是 --> name(命名),窗口,事件类型,事件相关联的响应函数
Mat frame;
for (;;) {
if (g_run != 0) {
g_cap >> frame;
if (frame.empty())break;
int current_pos = (int)g_cap.get(CAP_PROP_POS_FRAMES);
g_dontset = 1;
setTrackbarPos("Position", "test", current_pos);
imshow("test", frame);
g_run -= 1;
}
char c = (char)waitKey(10);
if (c == 's') {
g_run = 1;
cout << "Single step,run= " << g_run << endl;
}
if (c == 'r') {
g_run = -1;
cout << "Run mode, run= " << g_run << endl;
}
if (c == 27)break;//ESC
}
return 0;
}
它允许用户按下s键来执行单步模式,按下r键恢复连续视频播放模式。然和时候都可以通过滑动条跳转到视频一个新的时间点,我们用单步模式在那个时间点播放。
程序中用createTrackbar()来创建一个滑动条。这些代码理解起来没有太大困难。
接下来我们会对视频中的每一帧实现一些简单的操作。
一个最简单的操作就是对图像的平滑处理,通过高斯核或者其他核卷积效减小图像的信息量。
如下代码在第一个代码的基础上做了一下修改,得到两个窗口,一个显示原图,一个显示处理后的图(图像被5*5大小的高斯核模糊并且被写入out变量中)。高斯核的大小必须是奇数。程序执行了两次模糊操作
#include
#include
int main()
{
cv::namedWindow("in", cv::WINDOW_AUTOSIZE);
cv::namedWindow("out", cv::WINDOW_AUTOSIZE);
cv::Mat img = cv::imread("Hakurei Reimu.png");//图片路径
if (img.empty())return -1;
cv::imshow("in", img);
cv::Mat out;
cv::GaussianBlur(img, out, cv::Size(5, 5), 3, 3);
cv::GaussianBlur(out, out, cv::Size(5, 5), 3, 3);
cv::imshow("out", out);
cv::waitKey(0);
return 0;
}
这里我们先用pyrDown()来创建一个新的图像,其宽高均为原始图像的一半
#include
#include
int main()
{
cv::namedWindow("in", cv::WINDOW_AUTOSIZE);
cv::namedWindow("out", cv::WINDOW_AUTOSIZE);
cv::Mat img1 = cv::imread("Hakurei Reimu.png");//图片路径
if (img1.empty())return -1;
cv::Mat img2;
cv::imshow("in", img1);
cv::pyrDown(img1, img2);
cv::imshow("out", img2);
cv::waitKey(0);
return 0;
}
下面我们用Canny边缘检测器输出一个单通道的(灰色)图像,其中边缘检测器通过cvtColor()函数生成一个和原图一样大小但只有一个通道的图像,从而将图像从BGR图像转换为灰度图,这个操作在OpenCV中定义为宏COLOR_BGR2GRAY。
#include
#include
int main()
{
cv::namedWindow("gray", cv::WINDOW_AUTOSIZE);
cv::namedWindow("canny", cv::WINDOW_AUTOSIZE);
cv::Mat img1 = cv::imread("Hakurei Reimu.png");//图片路径
if (img1.empty())return -1;
cv::Mat img_gry,img_cny;
cv::cvtColor(img1, img_gry, cv::COLOR_BGR2GRAY);
cv::imshow("gray", img_gry);
cv::Canny(img_gry, img_cny, 10, 100, 3, true);
cv::imshow("canny", img_cny);
cv::waitKey(0);
return 0;
}
这里我们可以将这些操作组合起来,比如对图像收缩两次再寻找其边缘。然后通过一个简单的方法来读取结果的像素值
#include
#include
int main()
{
cv::namedWindow("gray", cv::WINDOW_AUTOSIZE);
cv::namedWindow("canny", cv::WINDOW_AUTOSIZE);
cv::Mat img1 = cv::imread("Hakurei Reimu.png");//图片路径
if (img1.empty())return -1;
cv::Mat img_gry,img_cny,pyr,pyr2;
cv::pyrDown(img1, pyr);
cv::pyrDown(pyr, pyr2);
cv::cvtColor(pyr2, img_gry, cv::COLOR_BGR2GRAY);
cv::imshow("gray", img_gry);
cv::Canny(img_gry, img_cny, 10, 100, 3, true);
cv::imshow("canny", img_cny);
int x = 16, y = 32;
cv::Vec3b intensity = img1.at<cv::Vec3b>(y, x);
uchar blue = intensity[0];
uchar green = intensity[1];
uchar red = intensity[2];
std::cout << "At(x,y)=(" << x << "," << y << "):(blue,green,red)=("
<< (unsigned int)blue << ", " << (unsigned int)green << " , " << (unsigned int)red
<< ")" << std::endl;
std::cout << "Gray pixel there is:" << (unsigned int)img_gry.at<uchar>(y, x) << std::endl;
x /= 4; y /= 4;
std::cout << "Pyramid2 pixel there is" << (unsigned int)pyr2.at<uchar>(y, x) << std::endl;
img_cny.at<uchar>(x, y) = 128;
cv::waitKey(0);
return 0;
}
int main(int argc,char** argv)
{
cv::namedWindow("test", cv::WINDOW_AUTOSIZE);
cv::VideoCapture cap;
if (argc == 1) {
cap.open(0);
}
else {
cap.open(argv[1]);
}
if (!cap.isOpened()) {
std::cerr << "could not open capture" << std::endl;
return -1;
}
cv::waitKey(0);
return 0;
}
同一个对象可以读取时评文件也可以连接摄像头。
连接一个摄像头需要给它一个相机ID号(如果只有一个摄像头连接,这个ID通常为0),ID的默认值为-1,这意味着随意选择一个。
我们可以创建一个写入对象将帧一次输入到一个视频文件中,通过对象VideoWriter。完成后调用VideoWriter.release()方法。
下面的这个程序会打开一个视频文件读取它的内容后将其转换为对数极坐标形式,然后将这种形式写入一个新的视频文件。
#include
#include
int main(int argc, char** argv) {
cv::namedWindow("test", cv::WINDOW_AUTOSIZE);
cv::namedWindow("log", cv::WINDOW_AUTOSIZE);
cv::VideoCapture cap("灵梦.mp4");
double fps = cap.get(cv::CAP_PROP_FPS);
cv::Size size(
(int)cap.get(cv::CAP_PROP_FRAME_WIDTH), (int)cap.get(cv::CAP_PROP_FRAME_HEIGHT)
);
cv::VideoWriter writer;
writer.open("leimo.mp4", CV_FOURCC('M', 'J', 'P', 'G'), fps, size);
cv::Mat log, bgr;
for (;;) {
cap >> bgr;
if (bgr.empty())break;
cv::imshow("test", bgr);
cv::logPolar(bgr, log, cv::Point2f(bgr.cols / 2, bgr.rows / 2), 40, cv::WARP_FILL_OUTLIERS);
cv::imshow("log", log);
writer << log;
char c = cv::waitKey(10);
if (c == 27)break;
}
cap.release();
}
---------------------------------------------------
参考:《学习OpenCV3》