OpenCV

git hub opencv 官页 4.x文档 API 文档

QT with OpenCV

参考一些其他: OpenCV 100问

python

pip install opencv-python

轮廓操作

轮廓的多边形逼近

轮廓的多边形逼近指的是: 使用多边形来近似表示一个轮廓; 多边形逼近的目的是为了减少轮廓的顶点数目; 多边形逼近的结果依然是一个轮廓, 只是这个轮廓相对要粗旷一些; 可以使用方法cvApproxPoly() approxPolyDP in short 模糊逼近轮廓边缘, 减少轮廓的顶点数目, 让轮廓不那么复杂

轮廓的关键点

轮廓的关键点是: 轮廓上包含曲线信息比较多的点; 关键点是轮廓顶点的子集; 可以使用cvFindDominantPoints 函数来获取轮廓上的关键点, 该函数返回的结果一个包含 关键点在轮廓顶点中索引 的序列;再次强调: 是索引, 不是具体的点;如果要得到关键点的具体坐标, 可以用索引到轮廓上去找;

轮廓的周长和面积

轮廓的周长可以用cvContourPerimeter或者cvArcLength函数来获取; 轮廓的面积可以用cvContourArea函数来获取;

轮廓的边界框

有三种常见的边界框: 矩形, 圆形, 椭圆; (1)矩形: 在图像处理系统中提供了一种叫Rectangle的矩形, 不过它只能表达边垂直或水平的特例; OpenCv中还有一种叫Box的矩形, 它跟数学上的矩形一致, 只要4个角是直角即可; 如果要获取轮廓的Rectangle, 可以使用cvBoundingRect函数; 如果要获取轮廓的Box, 可以使用cvMinAreaRect2函数; (2)圆形 如果要获取轮廓的圆形边界框, 可以使用cvMinEnclosingCircle函数; (3)椭圆 如果要获取轮廓的椭圆边界框, 可以使用cvFitEllipse2函数;

轮廓的矩

矩是通过对轮廓上所有点进行积分运算**(或者认为是求和运算) 而得到的一个粗略特征**;

我们可以使用cvContoursMoments函数, cvMoments函数方便的得到轮廓的矩集, 然后再相应的方法或函数获取各种矩; 特定的矩: cvGetSpatialMoment函数 中心矩: cvGetCentralMoment函数 归一化中心矩: cvGetNormalizedCentralMoment函数 Hu矩: cvGetHuMoments函数

轮廓的轮廓树

轮廓树用来描述某个特定轮廓的内部特征; 注意: 轮廓树跟轮廓是一一对应的关系; 轮廓树不用于描述多个轮廓之间的层次关系;

轮廓树的创建过程:

从一个轮廓创建一个轮廓树是从底端(叶子节点)到顶端(根节点)的; 首先搜索三角形突出或者凹陷的形状的周边(轮廓上的每一个点都不是完全和它的相邻点共线的)每个这样的三角形被一条线段代替, 这条线段通过连接非相邻点的两点得到; 因此实际上三角形或者被削平或者被填满; 每个这样的替换都把轮廓的顶点减少, 并且给轮廓树创建一个新节点; 如果这样的一个三角形的两侧有原始边, 那么她就是得到的轮廓树的叶子; 如果一侧已是一个三角形, 那么它就是那个三角形的父节点; 这个过程的迭代最终把物体的外形简称一个四边形, 这个四边形也被剖开; 得到的两个三角形是根节点的两个子节点;

结果的二分树最终将原始轮廓的形状性比编码; 每个节点被它所对应的三角形的信息所注释;

这样建立的轮廓树并不太鲁棒, 因为轮廓上小的改变也可能会彻底改变结果的树, 同时最初的三角形是任意选取的; 为了得到较好的描述需要首先使用函数cvApproxPoly()之后将轮廓排列(运用循环移动)成最初的三角形不怎么收到旋转影响的状态; 可以用函数cvCreateContourTree来构造轮廓树;

轮廓的凸包和凸缺陷

轮廓的凸包和凸缺陷用于描述物体的外形; 凸包和凸缺陷很容易获得; 如果要判断轮廓是否是凸的, 可以用cvCheckContourConvexity函数; 如果要获取轮廓的凸包, 可以用cvConvexHull2函数, 返回的是包含顶点的序列; 如果要获取轮廓的凸缺陷, 可以用cvConvexityDefects函数;

轮廓的成对几何直方图

成对几何直方图(pairwise geometrical histogram PGH)是链码编码直方图(chain code histogram CCH)的一个扩展或者延伸; CCH是一种直方图, 用来统计一个轮廓的Freeman链码编码每一种走法的数字; 这种直方图的一个优良性质为当物体旋转45度, 那么新直方图是老直方图的循环平移; 这样就可以不受旋转影响;

(1)轮廓保存的是一系列的顶点, 轮廓是由一系列线段组成的多边形; 对于看起来光滑的轮廓(例如圆), 只是线段条数比较多, 线段长度比较短而已; 实际上, 电脑中显示的任何曲线都由线段组成; (2)每两条线段之间都有一定的关系, 包括它们(或者它们的延长线)之间的夹角, 两条线段的夹角范围是: (0,180); (3)每两条线段上的点之间还有距离关系, 包括最短(小)距离, 最远(大)距离, 以及平均距离; 最大距离我用了一个偷懒的计算方法, 我把轮廓外界矩形的对角线长度看作了最大距离; (4)成对几何直方图所用的统计数据包括了夹角和距离;

轮廓的匹配

我们在上文中得到了轮廓的这么多特征, 它们也可以用于进行匹配; 典型的轮廓匹配方法有: Hu矩匹配, 轮廓树匹配, 成对几何直方图匹配;

  1. Hu矩匹配 轮廓的Hu矩对包括缩放, 旋转和镜像映射在内的变化具有不变性; cvMatchShapes函数可以很方便的实现对2个轮廓间的匹配;

  2. 轮廓树匹配 用树的形式比较两个轮廓; cvMatchContourTrees函数实现了轮廓树的对比;

  3. 成对几何直方图匹配 在得到轮廓的成对几何直方图之后, 可以使用直方图对比的方法来进行匹配;

参考- OpenCV轮廓、多边形逼近、关键点、周长和面积、边界框、矩、轮廓树、凹凸包、几何直方图、匹配

轮廓匹配:

IplImage* img_8uc1 = cvLoadImage("flower.jpg",CV_LOAD_IMAGE_GRAYSCALE);
IplImage* img_edge1 = cvCreateImage(cvGetSize(img_8uc1),8,1);
IplImage* img_8uc3 = cvCreateImage(cvGetSize(img_8uc1),8,3);
cvThreshold(img_8uc1,img_edge1,128,255,CV_THRESH_BINARY);
CvMemStorage* storage1 = cvCreateMemStorage();
CvSeq* first_contour1 = NULL;
int Nc = cvFindContours(    
        img_edge1,    
        storage1,    
        &first_contour1,    
        sizeof(CvContour),    
        CV_RETR_LIST    
        );
IplImage* img_8uc12 = cvLoadImage("flower1.jpg",CV_LOAD_IMAGE_GRAYSCALE);
IplImage* img_edge12 = cvCreateImage(cvGetSize(img_8uc12),8,1);
IplImage* img_8uc3 = cvCreateImage(cvGetSize(img_8uc1),8,3);
cvThreshold(img_8uc12,img_edge12,128,255,CV_THRESH_BINARY);
CvMemStorage* storage2 = cvCreateMemStorage();
CvSeq* first_contour2 = NULL;
int Nc2 = cvFindContours(    
        img_edge12,    
        storage2,    
        &first_contour2,    
        sizeof(CvContour),    
        CV_RETR_LIST    
        );
double n = cvMatchShapes(first_contour1,first_contour2,CV_CONTOURS_MATCH_I1,0);
printf("%d",n);
cvWaitKey();
IplImage* img_8uc1 = cvLoadImage("flower.jpg",CV_LOAD_IMAGE_GRAYSCALE);
IplImage* img_edge1 = cvCreateImage(cvGetSize(img_8uc1),8,1);
IplImage* img_8uc3 = cvCreateImage(cvGetSize(img_8uc1),8,3);
cvThreshold(img_8uc1,img_edge1,128,255,CV_THRESH_BINARY);
CvMemStorage* storage1 = cvCreateMemStorage();
CvSeq* first_contour1 = NULL;
int Nc = cvFindContours(    
      img_edge1,    
      storage1,    
      &first_contour1,    
     sizeof(CvContour),    
     CV_RETR_LIST    
      );
CvContourTree* tree1 = cvCreateContourTree(    
    first_contour1,    
   storage1,    
   200    
    );
IplImage* img_8uc12 = cvLoadImage("flower1.jpg",CV_LOAD_IMAGE_GRAYSCALE);
IplImage* img_edge12 = cvCreateImage(cvGetSize(img_8uc12),8,1);
IplImage* img_8uc3 = cvCreateImage(cvGetSize(img_8uc1),8,3);
cvThreshold(img_8uc12,img_edge12,128,255,CV_THRESH_BINARY);
CvMemStorage* storage2 = cvCreateMemStorage();
CvSeq* first_contour2 = NULL;
int Nc2 = cvFindContours(    
        img_edge12,    
        storage2,    
        &first_contour2,    
        sizeof(CvContour),    
        CV_RETR_LIST    
        );
CvContourTree* tree2 = cvCreateContourTree(    
        first_contour2,    
        storage2,    
        200    
        );
double n = cvMatchContourTrees(tree1,tree1,CV_CONTOURS_MATCH_I1,200);
printf("%d",n);
cvWaitKey();

几何直方图匹配

#include "gesrec.h"     
#include <stdio.h>    
//     
#define PI 3.14159f     
//轮廓面积比较函数     
static int gesContourCompFunc(const void* _a, const void* _b, void* userdata) {
	int retval;
	double s1, s2;
	CvContour* a = (CvContour*)_a;
	CvContour* b = (CvContour*)_b;
	s1 = fabs(cvContourArea(a));
	s2 = fabs(cvContourArea(b));
	//s1 = a->rect.height * a->rect.width;    
	//s2 = b->rect.height * b->rect.width;     
	if(s1 < s2) {
		retval = 1;
	} else if(s1 == s2) {
		retval = 0;
	} else {
		retval = -1;
	}
	return retval;
}
//src:BGR dst:    
void gesFindContours(IplImage* src, IplImage* dst, CvSeq** templateContour, CvMemStorage* templateStorage, int flag) {
	int count;
	//轮廓数    
	IplImage* gray;
	CvMemStorage* first_sto;
	CvMemStorage* all_sto;
	CvSeq* first_cont;
	CvSeq* all_cont;
	CvSeq* cur_cont;
	//初始化动态内存    
	first_sto = cvCreateMemStorage(0);
	first_cont = cvCreateSeq(CV_SEQ_ELTYPE_POINT, sizeof(CvSeq), sizeof(CvPoint), first_sto);
	all_sto = cvCreateMemStorage(0);
	all_cont = cvCreateSeq(0, sizeof(CvSeq), sizeof(CvSeq), all_sto);
	//创建源图像对应的灰度图像     
	gray = cvCreateImage(cvGetSize(src), IPL_DEPTH_8U, 1);
	cvCvtColor(src, gray, CV_BGR2GRAY);
	//得到图像的外层轮廓     
	count = cvFindContours(gray, first_sto, &first_cont, sizeof(CvContour), CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE);
	//如果没有检测到轮廓则返回     
	if(first_sto == NULL) {
		return;
	}
	//将所有的轮廓都放到first_cont中     
	for (;first_cont != 0;first_cont = first_cont->h_next) {
		if(((CvContour* )first_cont)->rect.height * ((CvContour* )first_cont)->rect.width >=625) cvSeqPush(all_cont, first_cont);
	}
	//对轮廓按照面积进行排序    
	cvSeqSort(all_cont, gesContourCompFunc, 0);
	//在dst中画出轮廓     
	cvZero(dst);
	for (int i = 0;i < min(all_cont->total, 3);i++)//次数待改 {
		cur_cont = (CvSeq* )cvGetSeqElem(all_cont, i);
		if(flag != 0 && i == 0) {
			*templateContour = cvCloneSeq(cur_cont, templateStorage);
		}
		CvScalar color = CV_RGB(rand()&255, rand()&255, rand()&255);
		cvDrawContours(dst, (CvSeq* )cur_cont, color, color, -1, 1, 8);
	}
	//判断原点位置以确定是否需要反转图像    
	if(src->origin == 1) {
		cvFlip(dst);
	}
	//释放内存    
	cvReleaseMemStorage(&first_sto);
	cvReleaseMemStorage(&all_sto);
	cvReleaseImage(&gray);
}
void gesMatchContoursTemplate(IplImage* src, IplImage* dst, CvSeq** templateContour) {
	CvSeq* contour;
	CvMemStorage* storage;
	//初始化动态内存    
	storage = cvCreateMemStorage(0);
	contour = cvCreateSeq(CV_SEQ_ELTYPE_POINT, sizeof(CvSeq), sizeof(CvPoint), storage);
	//得到轮廓并进行匹配    
	gesFindContours(src, dst, &contour, storage, 1);
	if(contour->total != 0)//如果得到的轮廓不为空 {
		double result = cvMatchShapes((CvContour* )contour, (CvContour* )(*templateContour), CV_CONTOURS_MATCH_I3);
		printf("%.2fn", result);
		//
	}
	//释放内存    
	cvReleaseMemStorage(&storage);
}
//模版匹配法的完整实现    
int gesMatchContoursTemplate2(IplImage* src, IplImage* dst, CvSeq* templateContour) {
	CvSeq* contour;
	CvSeq* cur_cont;
	CvMemStorage* storage;
	double minValue, tempValue;
	int i, minIndex;
	//初始化动态内存    
	storage = cvCreateMemStorage(0);
	contour = cvCreateSeq(CV_SEQ_ELTYPE_POINT, sizeof(CvSeq), sizeof(CvPoint), storage);
	//得到轮廓并进行匹配     
	minIndex = -1;
	gesFindContours(src, dst, &contour, storage, 1);
	if(contour->total != 0)//如果得到的轮廓不为空 {
		if(templateContour->total != 0) {
			cur_cont = (CvSeq* )cvGetSeqElem(templateContour, 0);
			minValue = cvMatchShapes((CvContour* )contour, (CvContour* )cur_cont, CV_CONTOURS_MATCH_I3);
			minIndex = 0;
			printf("0:%.2fn", minValue);
		}
		for (i = 1;i < templateContour->total;i++) {
			cur_cont = (CvSeq* )cvGetSeqElem(templateContour, i);
			tempValue = cvMatchShapes((CvContour* )contour, (CvContour* )cur_cont, CV_CONTOURS_MATCH_I3);
			if(tempValue < minValue) {
				minValue = tempValue;
				minIndex = i;
			}
			printf("%d:%.2fn", i, tempValue);
		}
		if(minValue >= 0.3) {
			minIndex = -1;
		}
	}
	//打印匹配结果    
	printf("the result is %dn", minIndex);
	//释放内存    
	cvReleaseMemStorage(&storage);
	return minIndex;
}
//找出轮廓最大的5个极大值点     
void gesFindContourMaxs(CvSeq* contour) {
	int i;
	CvScalar center;
	//重心位置     
	CvPoint* p;
	CvMat max;
	//存储5个极大值的数组     
	double initMax[] = {
		-1, -1, -1, -1, -1
	}
	;
	//初始极大值设置为-1     
	double minValue, maxValue;
	//5个极大值中的最大值与最小值     
	CvPoint minLoc;
	//最小值的位置     
	double preDistance = 0;
	bool isCandidate = false;
	//是否是候选的极大值点    
	//初始化重心位置     
	center = cvScalarAll(0);
	//初始化极大值矩阵     
	max = cvMat(1, 5, CV_64FC1, initMax);
	//首先求出轮廓的重心    
	for (i = 0;i < contour->total;i++) {
		p = (CvPoint* )cvGetSeqElem(contour, i);
		center.val[0] += p->x;
		center.val[1] += p->y;
	}
	center.val[0] /= contour->total;
	center.val[1] /= contour->total;
	//遍历轮廓,找出所有的极大值点     
	for (i = 0;i < contour->total;i++) {
		p = (CvPoint* )cvGetSeqElem(contour, i);
		double distance = sqrt(pow(center.val[0] - p->x, 2) + pow(center.val[1] - p->y, 2));
		if(distance > preDistance) {
			isCandidate = true;
		} else if(distance < preDistance && isCandidate == true) {
			cvMinMaxLoc(&max, &minValue, &maxValue, &minLoc);
			if(distance > minValue) {
				cvmSet(&max, minLoc.y, minLoc.x, distance);
			}
			isCandidate = false;
		} else {
			isCandidate = false;
		}
		preDistance = distance;
	}
	//打印5个极大值     
	printf("%.2f %.2f %.2f %.2f %.2fn", cvmGet(&max, 0, 0), cvmGet(&max, 0, 1), cvmGet(&max, 0, 2), cvmGet(&max, 0, 3), cvmGet(&max, 0, 4));
}
//计算轮廓的pair-wise几何直方图     
CvHistogram* gesCalcContoursPGH(CvSeq* contour) {
	CvHistogram* hist;
	//成对几何直方图    
	CvContour* tempCont;
	//得到成对几何直方图第二个维度上的范围     
	tempCont = (CvContour* )contour;
	cvBoundingRect(tempCont, 1);
	int sizes[2] = {
		60, 200
	}
	;
	float ranges[2][2] = { {
			0,PI
		}
		, {
			0,200
		}
	}
	;
	float** rangesPtr = new float* [2];
	rangesPtr[0] = ranges[0];
	rangesPtr[1] = ranges[1];
	//初始化几何直方图     
	hist = cvCreateHist(2, sizes, CV_HIST_ARRAY, rangesPtr, 1);
	//计算轮廓的成对几何直方图     
	cvCalcPGH(contour, hist);
	return hist;
}
//对轮廓的pair-wise几何直方图进行匹配    
void gesMatchContoursPGH(CvSeq* contour, CvHistogram* templateHist) {
	CvHistogram* hist;
	//得到轮廓的成对几何直方图    
	hist = gesCalcContoursPGH(contour);
	//归一化直方图    
	cvNormalizeHist(templateHist, 1);
	cvNormalizeHist(hist, 1);
	//直方图匹配     
	double result = cvCompareHist(hist, templateHist, CV_COMP_INTERSECT);
	printf("result:%.2fn", result);
	//释放内存    
	cvReleaseHist(&hist);
}
 

基于OTSU+凸包检测

OTSU 二值化

 
def adaptive_otsu(img_g):
    h, w = img_g.shape
    mask = np.zeros_like(img_g)
    winHalfWidth = 10
    localVarThresh = 0.002
 
    for i in range(0,w):
        new_img = img_g[:, max(1,i-winHalfWidth): min(w,i+winHalfWidth)]
        th , th_otsu = cv.threshold(new_img, 0, 255, cv.THRESH_OTSU)
        intile = np.var(new_img / 255)
        if intile > localVarThresh:
            _, mask[:,i:i+1] = cv.threshold(img_g[:,i:i+1], th, 255, cv.THRESH_BINARY)
        else:
            mask[:, i:i+1] = 255
 
 
 
 
 

形态学运算

此处采用3x3的核对图像开运算

kernel = np.ones((3, 3), np.uint8)
image_bin = cv.morphologyEx(mask, cv.MORPH_OPEN, kernel)

凸包检测 提取轮廓

求取凸包数量:

for i in range(len(contours)):
    cnt = contours[i]
    hull = cv.convexHull(cnt, returnPoints=False)

此处给定阈值T1(阈值根据自己实际情况设定),判断凸包数量是否进入下一步,检测缺陷点。

if len(hull) > T1:
	defects = cv.convexityDefects(cnt, hull)

如果defects数量>1符合条件将缺陷点连线,如下图所示:

cv.line(mask, defects_point[0], defects_point[1],0)  

消除粘连后画出所有轮廓外接矩,及中心点

cv.rectangle(image, (x, y), (x + w, y + h), (0, 255, 0))  # 绘制外接矩
cv.circle(image, (cx, cy), 1, (0, 0, 255), -1)  # 用圆点绘制目标重心

图像匹配

模版匹配

官方教程

模板匹配是一种高级的计算机视觉技术,可识别图像上与预定义模板匹配的部分。它是在整个图像上移动模板并计算模板与图像上被覆盖窗口之间的相似度的过程。

模板匹配是通过二维卷积实现的。在卷积中,输出像素的值通过将两个矩阵的元素相乘并对结果求和。其中一个矩阵代表图像本身,另一个矩阵是模板,为卷积核。

模板匹配和卷积原理很像,模板在原图像上从原点开始滑动,计算模板与(图像被模板覆盖的地方)的差别程度,这个差别程度的计算方法在opencv里有6种,然后将每次计算的结果放入一个矩阵里,作为结果输出。假如原图形是AxB大小,而模板是axb大小,则输出结果的矩阵是(A-a+1)x(B-b+1) .

优点:简单、直接 缺点:不具有旋转不变性、不具有尺度不变性

匹配计算方法

TM_SQDIFF:计算平方不同,计算出来的值越小,越相关 TM_CCORR:计算相关性,计算出来的值越大,越相关 TM_CCOEFF:计算相关系数,计算出来的值越大,越相关 TM_SQDIFF_NORMED:计算归一化平方不同,计算出来的值越接近0,越相关 TM_CCORR_NORMED:计算归一化相关性,计算出来的值越接近1,越相关 TM_CCOEFF_NORMED:计算归一化相关系数,计算出来的值越接近1,越相关

代码

import cv2
import numpy as np
import matplotlib.pyplot as plt
 
img = cv2.imread('./lena.jpg', 0)   # 表示灰度图
template = cv2.imread('./face.jpg', 0)
w, h = template.shape[1], template.shape[0]
# 六种方法 模板匹配
methods = ['cv2.TM_CCOEFF', 'cv2.TM_CCOEFF_NORMED', 'cv2.TM_CCORR', 
           'cv2.TM_CCORR_NORMED', 'cv2.TM_SQDIFF', 'cv2.TM_SQDIFF_NORMED']
i = -1
for method in methods:
    img2 = img.copy()
    res = cv2.matchTemplate(img, template, eval(method))# 匹配
    min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res)
    if method in ['cv2.TM_SQDIFF', 'cv2.TM_SQDIFF_NORMED']:
        top_left = min_loc
    else:
        top_left = max_loc
    bottom_right = (top_left[0] + w, top_left[1] + h)
    cv2.rectangle(img2, top_left, bottom_right, (0, 0, 255), 2)  # 画矩形
    
    load_1 = [231, 232, 233, 234, 235, 236]
    i += 1
    plt.subplot(load_1[i]), plt.imshow(img2, cmap = 'gray')  # 画子图
    plt.axis('off')
    plt.title(method, {'fontsize': 9})
    
plt.show()
cv2.waitKey(0)
cv2.destroyAllWindows()

特征匹配

Brute-Force匹配又称蛮力匹配, 将一组特征点中的每一个特征点描述符与另一组的最接近的特征点描述符匹配。

官页文档-Basics of Brute-Force Matcher

  1. 创建匹配器 BFMatcher(normType , crossCheck) normType: NORM_L1 , NORM_L2 (默认) , HAMMING1(用于ORB的描述子)… crossCheck : 是否进行交叉匹配,默认false

  2. 进行特征匹配 match = bf.match(des1,des2)

  3. 绘制匹配点 img = cv2.drawMatches(搜索图img1 , kp1 , 匹配图img2 , kp2,match)