OpenCV学习笔记

扫盲

首先,在图像和视频分析方面,我们应该了解一些基本的假设和范式。对现在每个摄像机的记录方式来说,记录实际上是一帧一帧地显示,每秒 30-60 次。但是,它们的核心是静态帧,就像图像一样。因此,图像识别和视频分析大部分使用相同的方法。有些东西,如方向跟踪,将需要连续的图像(帧),但像面部检测或物体识别等东西,在图像和视频中代码几乎完全相同。

接下来,大量的图像和视频分析归结为尽可能简化来源。这几乎总是起始于转换为灰度,但也可以是彩色滤镜,渐变或这些的组合。从这里,我们可以对来源执行各种分析和转化。一般来说,这里发生的事情是转换完成,然后是分析,然后是任何覆盖,我们希望应用在原始来源上,这就是你可以经常看到,对象或面部识别的“成品”在全色图像或视频上显示。然而,数据实际上很少以这种原始形式处理。有一些我们可以在基本层面上做些什么的例子。所有这些都使用基本的网络摄像头来完成,没有什么特别的:

  • 背景提取
  • 颜色过滤
  • 边缘检测
  • 用于对象识别的特征匹配
  • 一般对象识别
import cv2
import numpy as np
from matplotlib 
import pyplot as plt
img = cv2.imread('watch.jpg',cv2.IMREAD_GRAYSCALE)
plt.imshow(img, cmap = 'gray', interpolation = 'bicubic')
plt.xticks([]), plt.yticks([]) 
# to hide tick values on X and Y axisplt.plot([200,300,400],[100,200,300],'c',linewidth=5)plt.show()

请注意,你可以绘制线条,就像任何其他 Matplotlib 图表一样,使用像素位置作为坐标的。 不过,如果你想绘制你的图片,Matplotlib 不是必需的。 OpenCV 为此提供了很好的方法。 当你完成修改后,你可以保存,如下所示:

cv2.imwrite('watchgray.png',img)

除了起始行,处理来自视频的帧与处理图像是一样的。 我们来举例说明一下:

import numpy as np
import cv2

cap = cv2.VideoCapture(0) 
while(True): 
	ret, frame = cap.read() 
	# 在这里,我们定义一个新的变量gray,作为转换为灰度的帧。 
	# 注意这个BGR2GRAY。 
	# 需要注意的是,OpenCV 将颜色读取为 BGR(蓝绿色红色),但大多数计算机应用程序读取为 RGB(红绿蓝)
	gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) 
	# 尽管是视频流,我们仍然使用imshow
	cv2.imshow('frame',gray) 
	# 这个语句每帧只运行一次。 
	# 基本上,如果我们得到一个按键,那个键是q,我们将退出while循环,然后运行
	if cv2.waitKey(1) & 0xFF == ord('q'): 
		break
# 这将释放网络摄像头,然后关闭所有的imshow()窗口。
cap.release()
cv2.destroyAllWindows()

某些情况下,你可能实际上需要录制,并将录制内容保存到新文件中。

import numpy as np
import cv2
cap = cv2.VideoCapture(1)
fourcc = cv2.VideoWriter_fourcc(*'XVID')
out = cv2.VideoWriter('output.avi',fourcc, 20.0, (640,480))
while(True): 
	ret, frame = cap.read() 
	gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) 
	out.write(frame) 
	cv2.imshow('frame',gray) 
	if cv2.waitKey(1) & 0xFF == ord('q'): 
		break
cap.release()
out.release()
cv2.destroyAllWindows()
# 主要要注意的是正在使用的编解码器,以及在while循环之前定义的输出信息。
# 然后,在while循环中,我们使用out.write()来输出帧。 
# 最后,在while循环之外,在我们释放摄像头之后,我们也释放out。

现在我们知道如何操作图像和视频。 如果你没有网络摄像头,你可以使用图像甚至视频来跟随教程的其余部分。 如果你希望使用视频而不是网络摄像头作为源,则可以为视频指定文件路径,而不是摄像头号码

图上画图

在这个 Python OpenCV 教程中,我们将介绍如何在图像和视频上绘制各种形状。 想要以某种方式标记检测到的对象是相当普遍的,所以我们人类可以很容易地看到我们的程序是否按照我们的希望工作。

cv2.line(img,(0,0),(150,150),(255,255,255),15)
# 参数:图片,开始坐标,结束坐标,颜色(bgr),线条粗细。
cv2.rectangle(img,(15,25),(200,150),(0,0,255),15)
# 参数:图像,左上角坐标,右下角坐标,颜色和线条粗细。
cv2.circle(img,(100,63), 55, (0,255,0), -1)
# 参数是图像/帧,圆心,半径,颜色和
# 注意我们粗细为-1。 这意味着将填充对象,所以我们会得到一个圆。

cv2.imshow('image',img)
cv2.waitKey(0)
cv2.destroyAllWindows()

一些概念

维度与通道

通道

OpenCV中图像的通道可以是1、2、3和4。其中常见的是1通道和3通道,2通道和4通道不常见。

  • 1通道的是灰度图
  • 2通道的图像是RGB555和RGB565。2通道在程序处理中会用到,如傅里叶变换,可能会用到,一个通道为实数,一个通道为虚数,主要是编程方便。RGB是16位的,2个字节(5+6+5),第一个字节的前5位是R,后三位+第二个字节是G,第二个字节后5位是B,可见对原图进行压缩了。
  • 3通道的是彩色图像,比如RGB图像
  • 4通道的图像是RGBA,是RGB加上一个A通道,也叫alpha通道,表示透明度。PNG图像是一种典型的4通道图像。alpha通道可以赋值0到1,或者0到255,表示透明到不透明。

通道:就是某一坐标表示的数组有多少个。

比如单通道:P[0][0]=10,在坐标(0,0)处只表示一个数组。
同理双通道:P[0][0]={10,15},那么就是双通道。
我们常用的RBG(255,255,255)就是三通道。

那么在opencv下如何表示单通道还是多通道?

cvInitMatHeader(&mat,3,6,CV_32FC1,P); //P就是上面的那个数组

此函数的第四参数:CV_32FC1--------32表示32位,FC表示通道,1表示单通道(若2表示双通道)。

常用的函数如下:

  • cvGetD,cvSetD
  • cvGetReal1D,cvGetReal2D,cvGetReal3D,cvGetRealND
  • cvGet1D,cvGet2D,cvGet3D,cvGetND
  • cvSet*D 也有相应的函数系列
  • 这些函数的缺点是:效率低
  • 具有Real的函数只用在单通道。
  • cvGet1D,cvGet2D等。----2D表示维度。只用x、y坐标轴使用cvGet2D;有x、y、z坐标轴就使用cvGet3D。
维度

维度:就是我们平常所说的坐标轴。二维:x,y。。三维:x,y,z。。


基础类型

Point

cv::Point类是两到三个原语类型的容器。
其成员是通过变量名称x、y、z访问的,而不是通过下标访问。
Point类是通过自己的模板派生来的,这是一个基础模板;实际上由两个这样的模板,分别是给二维、三维的点提供的。
这些类的实例有cv::Point2i、cv::Point2f、cv::Point2d或cv::Point3i、cv::Point3f、cv::Point3d(在这里,最后一个字母表示构造该点所需要的原语,i是一个32位整形,f是一个32位浮点数,d是一个64位浮点数,还可以有无符号字符b和短整型s)。

操作示例
默认构造函数cv::Point2i p2;
cv::Point3i p3;
复制构造函数cv::Point3i p1( p );
注:若p为浮点型,则会自动取整
值构造函数cv::Point2i( x0, x1 );
cv::Point3d( x0, x1, x2 );
构造成固定向量类(cv::Vec3f) p;
成员访问p.x, p.y, p.z
点乘float x = p1.dot( p );
双精度点乘double x = p1.dot( p );
叉乘p1.cross( p );
注:只用于三维的点
判断一个点p是否在矩形r内p1.inside( r );
注:只用于二维的点

Size

Size类在实际操作时和Point类相似,可以进行互相转换。主要区别在于Size类中对应的成员是width和height,而不是x和y,并且不支持转换到固定向量类。Size类的别名有cv::Size、cv::Size2i和cv::Size2f,其中前两个都表示整型,最后一个表示单精度浮点型。

操作示例
默认构造函数cv::Size sz;
cv::Size2i sz;
cv::Size2f sz;
复制构造函数cv::Size2f sz2( sz1 );
值构造函数cv::Size2f sz( w, h );
成员访问sz.width;
sz.height;
计算面积sz.area();

Scalar

cv::Scalar是四维点类,是四维双精度向量的快速表示。cv::Scalar直接从固定向量类模板实例(cv::Vec<double, 4>)中继承而来,所以继承了所有向量代数操作、成员访问函数(比如[]操作符)和一些固定向量类的特性,如:其元素是通过整数下标来访问的。

操作示例
默认构造函数cv::Scalar s;
复制构造函数cv::Scalar s2( s1 );
值构造函数cv::Scalar s( x0 );cv::Scalar s( x0, x1, x2, x3 );
元素相乘s1.mul( s2 );
(四元数)共轭s.conj();
// return cv::Scalar( x0, -x1, -x2, -x3 );
(四元数)真值测试s.isReal();
// if x1 == x2 == x3 == 0{ return true; }

Rect

Rect类又称矩形类,包含Point类的成员x和y(代表矩形左上角的坐标)和Size类的成员width和height(代表矩形的大小),但并非从它们继承过来。

操作示例
默认构造函数cv::Rect r;
复制构造函数cv::Rect r2( r1 );
值构造函数cv::Rect( x, y, w, h );
由起始点和大小构造cv::Rect( p, sz );
由两个对角构造cv::Rect( p1, p2 );
成员访问r.x; r.y; r.width; r.height;
计算面积r.area();
提取左上角r.tl(); // top-left
提取右下角r.br(); // bottom-left
判断点p是否在矩形r内r.contains( p );
操作示例
矩形r1和矩形r2的交集cv::Rect r3 = r1 & r2;
r1 &= r2;
同时包含矩形r1和矩形r2的最小面积矩形cv::Rect r3 = r1 I r2;
r1 I= r2;
平移矩形r x个数量cv::Rect rx = r + x;
r += x;
// x为一个Point实例,左上角偏移量
扩大矩形r s大小cv::Rect rs = r + s;
r += s;
// s为一个Size实例,尺寸改变量
比较矩形r1和矩形r2是否相等bool eq = ( r1 == r2 );
比较矩形r1和矩形r2是否不相等bool ne = ( r1 != r2 );

Mat

关于Mat的第一件事是你不再需要手动分配其大小并且当你不需要它的时候你不再需要手动释放它。虽然这样做仍然是可能的,大多数 OpenCV 函数将手动分配其输出数据。还有一个额外的好处是如果传递一个已存在Mat对象,它已经为矩阵分配所需的空间,这段空间将被重用。也就是说我们在任何时候只使用与我们执行任务时所必须多的内存一样多的内存。

Mat本质上是由两个数据部分组成的类:
矩阵头:包含信息有矩阵的大小,用于存储的方法,矩阵存储的地址等
指针:指向包含了像素值的矩阵(可根据选择用于存储的方法采用任何维度存储数据)

矩阵头部的大小是恒定的。然而,矩阵本身的大小因图像的不同而不同,通常是较大的数量级。
因此,当你在您的程序中传递图像并在有些时候创建图像副本您需要花费很大的代价生成图像矩阵本身,而不是图像的头部。

OpenCV 是图像处理库,它包含大量的图像处理函数。若要解决的计算挑战,最终大部分时间你会使用库中的多个函数。

由于这一原因,图像传给库中的函数是一种常见的做法(复制矩阵会耗费时间和空间)。我们不应忘记我们正在谈论往往是计算量相当大的图像处理算法。我们想要做的最后一件事是通过制作不必要的可能很大的图像的拷贝进一步降低您的程序的速度。

为了解决这一问题 OpenCV 使用引用计数系统:

  • 每个Mat具有自己的矩阵头
  • 但矩阵指针指向同一个地址

其思想是Mat的每个对象具有其自己的头,但可能他们通过让他们矩阵指针指向同一地址的两个实例之间共享该矩阵。此外,拷贝运算符将只能复制矩阵头部,也还将复制指针到大型矩阵,但不是矩阵本身。

Mat A, C; // 仅创建了头部
A = imread(argv[1], CV_LOAD_IMAGE_COLOR); // 在此我们知道使用的方法(分配矩阵)
Mat B(A); //使用拷贝构造函数
C = A; //赋值运算符

// ==============================================

Mat D (A, Rect(10, 10, 100, 100) ); // 用矩形界定
Mat E = A(Range:all(), Range(1,3)); // 用行和列来界定

最后一个使用它的对象。这对于使用引用计数的机制,每当有人复制Mat对象的头,矩阵的计数器被增加。每当一个头被清除,此计数器被下调。当该计数器变为零,矩阵也就被释放了。因为有时会仍然也要复制矩阵的本身,存在着 clone() 或 copyTo() 函数

Mat F = A.clone();
 
Mat G;
 
A.copyTo(G);
Mat public Attrs
MatAllocator* allocator
 	// 如果需要创建一个新矩阵的内存空间,系统会调用MatAllocator类作为分配符进行内存的分配。
 	
int rows 
	// 返回矩阵行数

int cols
	//返回矩阵的列数
	
uchar * data
 	// 指向矩阵的数据单元的指针

const uchar* dataend
const uchar* datalimit
const uchar* datastart
 	// helper fields used in locateROI and adjustROI More...
	// 这些都是用来控制ROI区域,来获取一些图像的局部切片,减少计算量或者特殊需求的。
 
int dims
 	// 矩阵的维数,但是这里一般结果都是2,因为opencv好像存储多维矩阵也是通过二维矩阵来计,它和基本的channels不一样。
 
int flags
 
MatSize size
	// 返回矩阵大小
 
MatStep step
 	// 矩阵元素寻址
 	// step[i]是Mat类中十分重要的一个属性,表示第i维的总大小,单位字节
	// M.data指向存储这列的首地址(类似于数组名)
	// M.dims是总维度
UMatData * u
 	// interaction with UMat More...
Mat public Method
/*=============================================
Mat创建和修改
=============================================*/
static MatExpr Mat::eye(int rows, int cols, inttype)
	// 返回一个恒等指定大小和类型矩阵。

void Mat::create(int rows, int cols, int type)
	// 分配新的阵列数据 (如果需要)。
	

void Mat::resize(size_t sz, const Scalar& s)
	// 该方法更改矩阵的 行数 。如果矩阵重新分配,第一最少(Mat::rows,sz) 行数要保留下来

void Mat::reserve(size_t sz)
	// 保留一定数量的行的空间。

void Mat::push_back(const Mat& elem)
	// 将元素添加到矩阵的底部。
	// 其类型和列的数目必须和矩阵容器是相同的。

template<typename T> voidMat::pop_back(size_t nelems=1)
	// 该方法从底部的列表中删除一行或多行。
	
/*=============================================
ROI region of interest:定位与调整
=============================================*/
void Mat::locateROI(Size& wholeSize,Point& ofs) const
	// ROI:region of interest
	// 父矩阵内定位矩阵头

Mat& Mat::adjustROI(int dtop, int dbottom,int dleft, int dright)
	// 调整子阵大小及其在父矩阵中的位置。
	// 该方法是 Mat::locateROI() 的互补性方法。这些函数的典型应用是确定父矩阵中子阵的位置,然后以某种方式改变位置
	// 参数是平移量,而不是位置
	
/*=============================================
取值,区域
=============================================*/
Mat Mat::operator()(const Rect& roi) const
     // 提取矩形子阵。 mat()

template<typename T> T& Mat::at(int i)const
template<typename T> const T&Mat::at(int i, int j) const
template<typename T> const T&Mat::at(Point pt) const
template<typename T> const T&Mat::at(int i, int j, int k) const	
	// i –索引 0 维度
	// j – 1 维度的索引
	// k – 沿 2 维度的索引
	// pt – Point(j,i) 作为指定元素的位置。
	// idx – Mat::dims 数组的索引。
A.at<float>(k+4) 和 B.at<int>(2*i+1) 分别代替A.at<float>(0,k+4)和
B.at<int>(2*i+1,0)
	// 该模板方法返回指定数组元素的引用。为了具有更高的性能,索引范围检查只在调试配置下执行。
	// 请注意使用具有单个索引 (i) 的变量可以访问的单行或单列的2 维的数组元素。
	// 也就是比方说,如果A是1 x N 浮点矩阵和B是M x 1的整数矩阵,您只需编写

uchar* Mat::ptr(int i=0)
	// 该方法返回uchar*,或指向由输入指定矩阵   行   的指针。
	// 参看Mat::isContinuous()的中示例了解如何使用这些方法。

/*=============================================
矩阵头 CvMat  、  IplImage
=============================================*/
Mat::operator CvMat() const
	// 该运算符创建矩阵 CvMat 的头,而不复制的基础数据。引用计数未被考虑到此操作中。
	// 因此,您应该确保CvMat 头在使用的时候不释放原始矩阵。该运算符对于新旧OpenCV API混用是有用的.

Mat::operator IplImage() const
     // 运算符创建矩阵 IplImage 头,而不复制的基础数据。您应该确保使用IplImage头时不释放原矩阵。
     // 与Mat::operatorCvMat类似,该运算符在OpenCV新旧API混用中很有用

/*=============================================
获取矩阵特征:
元素数目、是否连续存储、矩阵元素大小、矩阵元素通道大小
、矩阵的元素类型、矩阵大小、是否为空
=============================================*/
bool Mat::isContinuous() const
     // 如果在每一行的结尾无间隙连续存储矩阵的元素,该方法返回 true。

size_t Mat::total() const
     // 该方法返回数组元素(如果该数组表示图像的像素数)的数目。

size_t Mat::elemSize() const
     // 该方法返回以字节为单位的矩阵元素大小。例如,如果矩阵类型是 CV_16SC3,该方法返回3*sizeof(short)或 6

size_t Mat::elemSize1() const
     // 该方法返回以字节为单位的矩阵元素通道大小,也就是忽略通道的数量。例如, 如果矩阵类型是 CV_16SC3,该方法返回 sizeof(short) 或 2。

int Mat::type() const
     // 该方法返回一个矩阵的元素类型。这是兼容CvMat 类型系统,像 CV_16SC3标识符或 16 位有符号的3 通道阵列,等等。

int Mat::depth() const
     // 该方法返回矩阵元素深度(每个单独的通道类型)的标识符

size_t const Mat::step1()
     // 该方法返回以矩阵的step除以Mat::elemSize1()。它对快速访问任意矩阵元素很有用。

Size Mat::size() const
     // 该方法返回一个矩阵大小:Size(cols, rows)。矩阵超过 2 维时返回大小为(-1,-1)。

 bool Mat::empty() const
     // 如果 Mat::total() 是 0 或 Mat::data 为 NULL,则方法返回 true。
     // 因为pop_back() 和 resize()方法M.total()= = 0,并不意味着M.data = =NULL。
	
/*=============================================
迭代器
=============================================*/

template<typename _Tp>MatConstIterator_<_Tp> Mat::begin() const
     // 该方法返回矩阵的只读或读写的迭代器。
     // 矩阵迭代器的使用和双向 STL 迭代器的使用是非常相似的

template<typename _Tp>MatIterator_<_Tp> Mat::end()
template<typename _Tp>MatConstIterator_<_Tp> Mat::end() const
     // 该方法返回矩阵只读或读写的迭代器,设置为紧随最后一个矩阵元素的点

/*=============================================
计数器 与 释放
=============================================*/
void Mat::addref()
	// 计数器参考。方法递增与矩阵数据关联的引用计数

void Mat::release()
	// 该方法递减与矩阵的数据关联的引用计数。
	// 当引用计数减为0时,矩阵的数据将被释放,数据和引用计数器指针设置为 NULL。
	// 如果矩阵头指向外部数据集 (见 Mat::Mat()), 引用计数为 NULL,并且该方法在这种情况下无效。

详解


OP

在 OpenCV 教程中,我们将介绍一些我们可以做的简单图像操作。 每个视频分解成帧。 然后每一帧,就像一个图像,分解成存储在行和列中的,帧/图片中的像素。 每个像素都有一个坐标位置,每个像素都由
颜色值组成。 让我们列举访问不同的位的一些例子。

我们将像往常一样读取图像(如果可以,请使用自己的图像,但这里是我在这里使用的图像):

import cv2
import numpy as np
img = cv2.imread('watch.jpg',cv2.IMREAD_COLOR)

# 现在我们可以实际引用特定像素,像这样:
px = img[55,55]
# 下面我们可以实际修改像素:
img[55,55] = [255,255,255]
# 之后重新引用
px = img[55,55]
print(px)
# 下面我们可以引用 ROI,图像区域:
px = img[100:150,100:150]
print(px)
# 我们也可以修改 ROI,像这样:
img[100:150,100:150] = [255,255,255]
# 引用我们的图像的特定特征:
print(img.shape)
print(img.size)
print(img.dtype)

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值