计算多边形的最小包围矩形

在opencv中可以根据多边形的顶点计算一个最小包围矩形。这个矩形可以是任意位置和方向的,不拘泥与必须是长宽都要水平和竖直。

请看下图:

其中蓝色的矩形和红色的矩形都是中间那块像素块的包围矩形,显然红色的矩形面积更小。有时候我们就需要找到一个像素块的最小包围矩形来进行测量,对齐或者其他的一些应用。opencv中提供了minAreaRect函数来处理此问题。

函数原型

以下是python版本的函数原型:
(center(x, y), (width, height), angle of rotation) = cv2.minAreaRect(points)

从这里可以看出,minAreaRect函数接收的是一串像素点坐标,然后再给出一个矩形的参数。这里的points一般指的是一个像素块的包围多边形,而不是该像素块所有像素点的集合。一般此函数会和cv2.findContours函数配合使用。在检测出矩形后,如果我们想把该矩形的四个点坐标都提取出来,可以采取如下方案:

rect = cv2.minAreaRect(cnt)
box = cv2.boxPoints(rect)
box = np.int0(box)

检索图形周多边形

这个功能一般用在检索mask图形区域的周边多边形。findContours函数是opencv中很基本的一个函数。此外将对其应用做简要介绍。

检索高亮区块

findContours函数一般都检索高亮像素块的边界。例如下图所示:

此图中,从左到右依次是:原图,灰度图,阈值取反分割图,周边多边形图。图中数字部分高亮的时候,就会被检测成边界。这相当于在对图做threshold运算时采用了TRESH_BINARY_INV运算。

这个示例是为了提示我们,在应用cv2.findContours函数时,要让感兴趣的部分高亮显示。如果反过来,让背景高亮,内容黑暗(白板黑子)则会发生错误。如下图所示:

函数原型

findContours函数的原型如下:


void cv::findContours	(	
  InputArray 	image,
  OutputArrayOfArrays 	contours,
  OutputArray 	hierarchy,
  int 	mode,
  int 	method,
  Point 	offset = Point() 
)		

其中输入参数image是一般的图形对象,contours是检索出来的周边多边形。结构一般为这样的一个数组:[[[x00,y00], [x01,y01], ...],[[x10,y10],[x11,y11],...], ...],数组中0轴上,每一个对象都是一个封闭的多边形。

hierarchy指的是contours中多边形的层次关系。其数据结构为这样一个数组[[next0, previous0, child0, parent0], [next1, previous1, child1, parent1], ...]。这里的next, previous, child, parent都是整数类型,用来指示contours对应的多边形序号。next和previous指的的多边形的顺序关系,child, parent指的是包含与被包含的关系。

函数中mode的值,影响了识别多边形的结构组成方式。mode的取值有如下几种情况:

cv.RETR_TREE,这种情况会检索出所有的区块边界多边形,并对区块的包含与被包含形成树形结构。

cv.RETR_EXTERNAL, 这种情况只检索各区块的最外侧边界多边形。因此在hierarchy中不会形成child, parent的结构,并且在cv.RETR_TREE中能找出的所有parnet不为-1的多边形在cv.RETR_EXTERNAL的情况下都不会被检测出来

cv.RETR_CCOMP, 这种情况下检索算法也会像cv.RETR_TREE一样检索出所有的边界多边形。不同的是,它不会形成树形结构,而只会形成两层结构。在第一层里面只会有最外层多边形,第二层都是第一层中出现多边形的第一个内层多边形。如果在第二层中还有嵌套多边形,则此多边形会归入第一层中。

method参数用来指示检索边界时采用的算法,method可能取cv.CHAIN_APPROX_NONE, cv.CHAIN_APPROX_SIMPLE。前者不会对检测出来的多边形做任何的简化或者说近似的计算。而后者会对检测出来的多边形做简化运算,例如检测出来的一个像素区块时正方形时,前者的多边形可能会包含这个正方形周长上面经过的所有像素点,而后者的多边形可能就只有多边形4个角的顶点。

基于阀值的图形分割

基于阈值可以给图形做分隔,提取出我们需要的像素点。用来制作mask或者分离图片的背景等都有很多实际的应用。

cv2.threshold

最基本的定义一个全局阈值,然后对整体图片应用这个阈值。可以用来生成mask然后再用这个mask来做其他的操作。更进一步的,此算法还可以使用Otsu优化过程,让算法自己来决定阈值。要注意的是,Otsh算法它是假设这个图片的前景与背景区分得非常分明。它根据对整体图片的统计,然后找到两个最大的像素频率,并假设此图的背景与前景就对应这两个最大频率。然后用这两个最大频率出现的像素数值来计算阈值。因此Otsh算法在前景和背景有好几个像素集中区域时就会出现问题。

cv2.adaptiveThreshold

与常规的全局应用阈值不同,这里的(adaptiveThreshold)是调整式的应用阈值。它会应用局部的阈值来针对局部的像素来进行运算和判断。

此算法实现的基本思路,是对每一个像素的一定范围内的领域找一种平均值出来。然后用这个平均值减去一个常数,最后得到一个判据数值。然后在对比该像素与此判据数值的大小,并以此来判断该点是背景还是前景。对图像中所有点都应用此算法,最后得到一个新的图。如下图所示,计算图中红色所标识像素点是前景还是背景时,则需要综合计算黄色包围圈内所有像素的一种平均值来考虑。一般情况下,我们可以使用算数平均值或者高斯平均值,采用算数平均值时,该黄色包围圈内所有像素点对平均值的贡献一样,而使用高斯平均值时,在黄色包围圈内,距离该点越近,则对该点平均值的贡献越大。

我们可以看看opencv中adaptiveThreshold函数的原型:


void cv::adaptiveThreshold	(	
  InputArray 	src,
  OutputArray 	dst,
  double 	maxValue,
  int 	adaptiveMethod,
  int 	thresholdType,
  int 	blockSize,
  double 	C 
)	

其中,src, dst是输入图和输出图片,maxValue是判断为前景时填充的像素值,adaptiveMethod值的是前面提到的求相邻点平均值的算法。主要值的就是算数平均值和高斯平均值。thresholdType指的是如何填充0和maxValue的方法。或者是说,如何区分前背景的方法。至于后面的blockSize是前面提到算法中要采用的领域的大小。图示示例中,领域大小为7。C为计算出来平均值后再减去的那个常数。

更多基于神经网络的图形分割

对于一些更难以区分前背景的图片的应用场景,我们还可以使用人工智能的方法,把独立对象都识别出来,并给这些独立对象用于图形分段。此类方法已经超出此文要讨论问题(基于阈值)的范畴。要了解更多,请检索关键词:Mask R-CNN , U-Net segmentation network

使用opencv 的remap函数重构图片

remap函数是opencv中的一个魔法函数,借助它的功能,我们能够建立一张图片到另外一张图片的映射。对图片进行变形操作。

我们假设有一个这样的任务:将图片A映射成图片B,图片B的每一个像素点都可以在图片A中找到唯一的一个原像像素。

opencv中的remap就是一个专门完成此任务的函数。我们假设图片b的一个像素(xb,yb)在图片a中的原像素为(xa, ya),那么, remap是通过了定义了两个参数矩阵map0和map1来完成此任务。其中map0(xb, yb) = xa, map1(xb,yb) = yb。可见,map0的值将图片b相对应同样位置的值,映射到图片a的x坐标;map1的值将图片b相对应同样位置的值映射到图片a的y坐标。

最后一个需要注意的是,在矩阵中,y对应的是第0维度数据的索引, x对应的是第1维度数据的索引。