[学习笔记]opencv
[学习笔记]OpenCV
基于Python,By Laoair
基础操作
图片读取
# 一个是打开图片文件,一个是打开图片数据类型的对象
def readImg(src,code): ## code:0,grey;1,color
img = cv2.imread('./img/1.jpg', code)
cv2.imshow('test', img)
cv2.waitKey(0)
cv2.destroyAllWindows()
def showImg(img):
cv2.imshow('test', img)
cv2.waitKey(0)
cv2.destroyAllWindows()
void readImg(src,code=1)
{
Mat img = imread(src,code);
imshow(winName,img);
waitKey(0);
destroyAllWindows();
}
void showImg(Mat img,string winName="test")
{
imshow(winName,img);
waitKey(0);
destroyAllWindows();
}
视频读取
def readVideo(src,code):## code:6,grey;1,color
vc = cv2.VideoCapture(src)
if vc.isOpened():
open,frame = vc.read()
else:
open = False
while open:
ret, frame = vc.read()
if frame is None:
break
if ret == True:
value = cv2.cvtColor(frame,code)
cv2.imshow('result',value)
if cv2.waitKey(10) & 0xFF ==27:
break
vc.release()
cv2.destroyAllWindows()
VideoCapture cap(src);
Mat frame;
while (1)
{
cap >> frame;
//cap.read(frame)
if (img.empty())break;
imshow("img", img);
if (27 == waitKey(20))break;
}
cap.release();//释放资源
摄像头帧率
- 实时计算
def fpsShow(src):
cap = cv2.VideoCapture(src) # 导入的视频所在路径
start_time = time.time()
counter = 0
fps = cap.get(cv2.CAP_PROP_FPS) # 视频平均帧率
while cap.isOpened():
ret, frame = cap.read()
counter += 1 # 计算帧数
if (time.time() - start_time) != 0: # 实时显示帧数
cv2.putText(frame, "FPS {0}".format(float('%.1f' % (counter / (time.time() - start_time)))), (20, 50),
cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255),
3)
cv2.imshow('frame', frame)
print("FPS: ", counter / (time.time() - start_time))
counter = 0
start_time = time.time()
time.sleep(1 / fps) # 按原帧率播放
# 键盘输入空格暂停,输入q退出
key = cv2.waitKey(1) & 0xff
if key == ord(" "):
cv2.waitKey(0)
if key == 27:
break
cap.release()
cv2.destroyAllWindows()
- 读取摄像头自身属性
vc = cv2.VideoCapture(0)
fps = vc.get(cv2.CAP_PROP_FPS)
print(fps)
图片截取
def cropImg(img,x1,x2,y1,y2):
cimg=img[x1:x2,y1:y2]
return cimg
颜色提取
def splitColor(img):
b,g,r=cv2.split(img)
return b,g,r
vector<Mat>channels;
split(img,channels);//通道分离
Mat blue=channels.at(0);
Mat green=channels.at(1);
Mat red=channels.at(2);
颜色设置
def imgResetColor(img,color):# color:0,b;1,g;2,r
img[:,:,color]=0
return img
图层合并
def imgMerge(b,g,r):
img=cv2.merge((b,g,r))
return img
merge(channels,dst);
边界填充
def imgFillBorder(img,top,bottom,left,right,type):# type 是 已被定义的宏
cv2.copyMakeBorder(img,top,bottom,left,right,type)
return img
数值计算
- 直接相加
- 255溢出
- 函数相加 :cv2.add(imga,imgb)
- 255不溢出
- 函数权重add : cv2.addWeighted(imga,powera,imgb,powerb)
addWeighted(img1,0.5,img2,0.5,0,dst);
dst=5*img1;//增加曝光
dst=img1/5;//降低曝光
bitwise_and(img1,img2,dst);//逻辑与,求交集
bitwise_or(img1,img2,dst);//逻辑或,求并集
bitwise_not(img1,dst);//逻辑非,求补集
bitwise_xor(img1,img2,dst);//异或,相同为0,相异为1
ROI
img = img(Range(30,400),Range(30,400));
img = img(Rect(10,20,300,500));
图片尺寸
resize(src,dst,Size(300,300));
形态学
应用
- 走迷宫:膨胀腐蚀应用之走迷宫
卷积核
Mat element=getStructuringElement(MORPH_RECT,Size(5,5));
Mat element2=getStructuringElement(MORPH_CROSS,Size(5,5));
Mat element3=getStructuringElement(MORPH_ELLIPSE,Size(5,5));
- element
- 11111
- 11111
- 11111
- 11111
- 11111
- element2
- 00100
- 00100
- 11111
- 00100
- 00100
- element3
- 01110
- 11111
- 11111
- 11111
- 01110
腐蚀
def imgErosiion(img):
kernel = np.ones( (5,5) ,np.uint8 )
erosion = cv2.erode(img,kernel,iterations=1) # iterations 操作次数
return erosion
erode(src,dst,element,Point(-1,-1),2);//腐蚀两次 (-1,-1)默认中心位置
膨胀
def imgDilate(img):
kernel = np.ones((5, 5), np.uint8)
dilate = cv2.dilate(img, kernel, iterations=1)
return dilate
dilate(src,dst,element,Point(-1,-1),1); // 膨胀一次
开运算与闭运算
-
开运算: 先腐蚀再膨胀,相当于 去除毛刺
-
开操作是一般使对象的轮廓变得光滑,断开狭窄的间断和消除细的突出物
-
def imgOpening(img): kernel = np.ones((5, 5), np.uint8) opening = cv2.morphologyEx(img, cv2.MORPH_OPEN, kernel) return opening
morphologyEx(srcImg, dstImg, MORPH_OPEN, element); //开运算 -
-
闭运算: 线膨胀再腐蚀,相当于 放大毛刺
-
闭操作可使轮廓线更光滑,通常消弥狭窄的间断和长细的鸿沟,消除小的空洞,并填补轮廓线中的断裂。
-
def imgClosing(img): kernel = np.ones((5, 5), np.uint8) closing = cv2.morphologyEx(img, cv2.MORPH_CLOSE, kernel) return closing
morphologyEx(srcImg, dstImg, MORPH_CLOSE, element); //闭运算 -
梯度运算
- 梯度=膨胀-腐蚀, 得到 图片轮廓信息
def imgGradient(img):
kernel = np.ones((7,7), np.uint8)
gradient = cv2.morphologyEx(img,cv2.MORPH_GRADIENT,kernel)
return gradient
morphologyEx(srcImg, dstImg, MORPH_GRADIENT, element); //形态学梯度运算
礼(顶)帽与黑帽
-
礼(顶)帽 :原始输入-开运算结果;突出明亮区域
-
def imgTophat(img): kernel = np.ones((7, 7), np.uint8) tophat = cv2.morphologyEx(img,cv2.MORPH_TOPHAT,kernel) return tophat
morphologyEx(srcImg, dstImg, MORPH_TOPHAT, element); //顶帽运算 -
-
黑帽 :闭运算结果-原始输入
-
def imgBlackhat(img): kernel = np.ones((7, 7), np.uint8) blackhat = cv2.morphologyEx(img,cv2.MORPH_BLACKHAT,kernel); return blackhat
morphologyEx(srcImg, dstImg, MORPH_BLACKHAT, element); //黒帽运算 -
Sobel算子
- 计算图像梯度,并呈现再图片上
def imgSobel(img,dx,dy):
sobel=cv2.Sobel(img,cv2.CV_64F,dx,dy,ksize=3)
sobel=cv2.convertScaleAbs(sobel) # 取绝对值
return sobel
Sobel(src, dst_x, CV_16S, 1, 0, 3);
Sobel(src, dst_y, CV_16S, 0, 1, 3);
convertScaleAbs(dst_x, abs_grad_x);
convertScaleAbs(dst_y, abs_grad_y);
addWeighted(abs_grad_x 0.5, abs_grad_y, 0.5, 0, dst);
Scharr算子
- 相对于Sobel更敏感
def imgScharr(img,dx,dy):
scharr = cv2.Scharr(img,cv2.CV_64F,dx,dy)
scharr = cv2.convertScaleAbs(scharr)
return scharr
Laplacian算子
- 相对于Sobel更迟钝
def imgLaplacian(img):
laplacian = cv2.Laplacian(img,cv2.CV_64F)
laplacian = cv2.convertScaleAbs(laplacian)
return laplacian
Laplacian(src, dst, CV_16S, 3);
convertScaleAbs(dst, abs_dst);
平滑处理
- 用于处理 去除噪音点,滤波操作
均值滤波
# 当normalize=True时,二者完全按相同;溢出会归一化
def imgBlur(img,dx,dy):
blur = cv2.blur(img,(dx,dy))
return blur
def imgBox(img,dx,dy):
box = cv2.boxFilter(img,-1,(dx,dy),normalize=True)# 第二个参数指定-1为与源数据相同;第四个参数是是否取均值,不取均值可能导致数据溢出
return box
boxFilter(src,dst,-1,Size(3,3),Point(-1,-1),true);
blur(src,dst,Size(5,5));
高斯滤波
- 首选,稳定,好使
- cv2.GaussianBlur(src, ksize[,sigmaX, sigmaY[,, borderType]])-> dst
- src输入图像
- dst输出图像的大小和类型与src相同。
- ksize高斯内核大小。
- sigmaX X方向上的高斯核标准偏差
- sigmaY Y方向上的高斯核标准差
def imgAussian(img,dx,dy):
aussian = cv2.GaussianBlur(img,(dx,dy),1)# 第三个参数 高斯滤波器的尺寸
return aussian
GaussianBlur(src,dst,Size(5,5),1);
中值滤波
- 用于去除椒盐噪
def imgMedian(img,ksize):
median = cv2.medianBlur(img,ksize)
return median
medianBlur(src,dst,5);
双边滤波
- 保留边框,去除内容的噪点,磨皮
bilateralFilter(src,dst,5,10.0,2.0);
阈值(二值化)
-
阈值操作 并不能 绝对二值化
-
Canny 边缘处理后 为 绝对二值图
-
res , dst = cv2.threshold(img,thresh,maxval,type)
- res : True或False,代表是否读到图片
- img: 输入图,只能输入单通道图像,通常来说为灰度图
- dst : 输出图
- thresh:阈值
- maxval:当像素值达到了一定条件,所赋予的值
- type:操作类型,见下
- cv2.THRESH_BINARY:超过阈值部分取maxval,否则取0
- cv2.THRESH_BINARY_INV: 对上一个的反转
- cv2.THRESH_TRUNC:大于阈值部分取maxval,否则不变-----------更多空白,降低亮度
- cv2.THRESH_TOZERO:大于阈值部分不变,否则取0
- cv2.THRESH_TOZERO_INV 对上一个的反转
-
dst = cv2.inRange(src,lowerb,upperb]
- src:输入图片,Mat类型
- lowerb:一个三通道颜色,比如(0,0,255);图像中低于这个lower的值,图像值变为0
- upperb:一个三通道颜色,比如(0,0,255);图像中高于这个upper的值,图像值变为0
- dst:二值化图片
threshold(src,dst,100,255,CV_THRESH_BINARY);
adaptiveThreshold(src,dst,255,CV_ADAPTIVE_THRESH_MEAN_C,CV_THRESH_BINARY,11,5); //求均值
adaptiveThreshold(src,dst,255,CV_ADAPTIVE_THRESH_GAUSSIAN_C,CV_THRESH_BINARY,11,5); // 加权求平均
Canny边缘检测
- 1)去噪:使用高斯滤波器,以平滑图像,滤除噪声
- 2)计算梯度:计算图像中每个像素点的梯度强度和方向
- 强度计算 G = S*A ; S是权值,A是实际值
- 方向计算 θ = arctan( G_y / G_x )
- 3)应用非极大值抑制(NMS),以消除边缘检测带来的杂散响应
- 线性插值法
- 简化计算:直接比较方向上各点的值进行抑制
- 4)应用双阈值检测来确定真实的和潜在的边缘
- 阈值以上为边界
- 阈值以内判断是否联通
- 阈值以下忽略
- 5)通过抑制鼓励的若边缘最终完成边缘检测
edges = cv2.Canny( image, threshold1, threshold2[, apertureSize[, L2gradient]])
'''
--------------param---------------
image: 图片源,只能是单通道图像
threshold1:低阈值
threshold2:高阈值
apertureSize:算子大小
L2gradient:为计算图像梯度幅度的标识。其默认值为 False。如果为 True,则使用更精确的 L2 范数进行计算(即两个方向的导数的平方和再开方),否则使用 L1 范数(直接将两个方向导数的绝对值相加)。
edges: 二值图
----------------------------------
---------------note---------------
----注意阈值越高,对边界的要求就越高----
----降低阈值可以得到更精细的边界图片----
----------------------------------
'''
Canny(srcImg, dstImg, 30, 80);
轮廓
-
在黑色背景中找白色物体
-
得到轮廓信息的函数
-
(binary ,)contours, hierarchy = cv2.findContours(img,mode,method)
- binary : 等效img ; (新版本不返回这个值)
- contours:轮廓列表
- hierarchy:层级,了解即可
- img:二值图,该函数只接受二值图,否则报错
- mode:轮廓检索模式
- cv2.RETR_EXTERNAL:只检索最外面的轮廓
- cv2.RETR_LIST:检索所有的轮廓,并将其保存到一条链表当中
- cv2.RETR_CCOMP:检索所有的轮廓,并将它们组织为两层:顶层是各部分的外部边界,第二层是空洞的边界
- cv2.RETR_TREE(一般情况下):检索所有的轮廓,并重构嵌套轮廓的整个层次
- method:轮廓逼近方法(以下为常用)
- cv2.CHAIN_APPROX_NONE:以Freeman链码的方式输出轮廓,所有其他方法输出多边形(顶点的序列):相当于除了点还有线
- cv2.CHAIN_APPROX_SIMPLE:压缩水平的、垂直的和斜的部分,也就是,函数只保留他们的终点部分;相当于只画顶点
img = cv2.imread(imgSrc2)
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY) # 转灰度图片
ret, thresh = cv2.threshold(gray,127,255,cv2.THRESH_BINARY) # 转二位图,提高精确度
contours, hierarchy = cv2.findContours(thresh,cv2.RETR_TREE,cv2.CHAIN_APPROX_NONE) # 轮廓检测
# 绘制轮廓
drawImg = img.copy()
res = cv2.drawContours(drawImg,contours,-1,(0,0,255),2)
showImg(res)
# 轮廓近似
cnt = contours[2] # 取出某一个轮廓信息
epsilon = 0.15*cv2.arcLength(cnt,True)
approx = cv2.approxPolyDP(cnt,epsilon ,True)
drawImg = img.copy()
res = cv2.drawContours(drawImg,[approx],-1,(0,0,255),2)
# showImg(res)
# 轮廓边框
x,y,w,h = cv2.boundingRect(cnt)
img = cv2.rectangle(img,(x,y),(x+w,y+h),(0,255,0),2)
# showImg(img)
vector<vector<Point>> contours;
vector<Vec4i> hierarchy;
findContours(srcImg, contours, hierarchy,RETR_LIST, CHAIN_APPROX_SIMPLE ); //查找所有轮廓
drawContours(tempImg, contours,-1, Scalar(0, 255, 0),2); //绘制轮廓:-1代表绘制所有轮廓
// 遍历轮廓每一个点
for(int i=0; i<contours.size(); i++)
for(int j=0; j<contours[i].size(); j++)
circle(temp,Point(contours[i][j].x,contours[i][j].y),3,Scalar(0,255,0),2,8);
- 一些相关的东西
# 轮廓 本质上是 点集
# 而 opencv 有很多专门处理 点集 的函数
----------------1--------------------
area = cv2.contourArea(cnt)
# 计算轮廓面积
----------------2--------------------
perimeter = cv2.arcLength(cnt,True)
# 轮廓周长
# 第二个参数用来指定对象的形状是闭合(True)还是打开(False)
----------------3--------------------
epsilon = 0.1*cv2.arcLength(cnt,True)
approx = cv2.approxPolyDP(cnt,epsilon,True)
# 轮廓近似
# epsilon表示从原始轮廓到近似轮廓的最大距离,它是一个准确度参数
# epsilon越小,近似图像越复杂,也就越契合源图像
# 第三个参数True为闭合
# approxPolyDP的返回值也为 点集 , 但是近似图形的 顶点列表
# 近似图像 点与点直接肯定是图像 ,所以返回顶点列表即可
-----------------4---------------------
(x, y, w, h) = cv2.boundingRect(cnt)
# 获得一个图像的最小矩形边框一些信息
# 返回一个元组,包含 左上角顶点的(x,y)和矩形的宽高
矩(Moments)
- Moments moments(InputArray array, bool binnaryImage = false)
- array,一个点集,轮廓
- binnaryImage,是否为二值图像。默认为 false。若此值为 true,则所有非零像素均为 1,需注意的是,此参数仅对图像使用。
- 返回值为 Moments 类型对象(矩),在python中是字典
- 例如,{'m00': 0.0, 'm10': 0.0, 'm01': 0.0, 'm20': 0.0, 'm11': 0.0,.........}
- 计算 几何中心
- Cx = M10/M00
- Cy = M01/M00
img = cv2.imread(imgSrc2,0)
thresh = cv2.threshold(img,110,255,cv2.THRESH_BINARY_INV)[1]
cnts= (cv2.findContours(thresh,cv2.RETR_TREE,cv2.CHAIN_APPROX_NONE))[0]
M = cv2.moments(cnts[0])
cx,cy=int(M['m10']/M['m00']),int(M['m01']/M['m00'])
cv2.circle(img,(cx,cy),4,(130,170,0),3)
showImg(img)
直线拟合
- output = cv2.fitLine(InputArray points, distType, param, reps, aeps)
- InputArray Points:必须是nparray类型的点集(一般的数列直接报错噢)
- distType: 距离类型。fitline为距离最小化函数,拟合直线时,要使输入点到拟合直线的距离和最小化。
- cv2.DIST_USER : User defined distance
- cv2.DIST_L1: distance = |x1-x2| + |y1-y2|
- cv2.DIST_L2: 欧式距离,此时与最小二乘法相同;常用
- cv2.DIST_C:distance = max(|x1-x2|,|y1-y2|)
- cv2.DIST_L12:L1-L2 metric: distance = 2(sqrt(1+x*x/2) - 1))
- cv2.DIST_FAIR:distance = c^2(|x|/c-log(1+|x|/c)), c = 1.3998
- cv2.DIST_WELSCH: distance = c2/2(1-exp(-(x/c)2)), c = 2.9846
- cv2.DIST_HUBER:distance = |x|<c ? x^2/2 : c(|x|-c/2), c=1.345
- param: 距离参数,跟所选的距离类型有关
- 值可以设置为0。
- 实际上,写0的话,系统会自动给你用最优的参数。
- reps, aeps: 第5/6个参数用于表示拟合直线所需要的径向和角度精度,通常情况下两个值均被设定为1e-2.
- output : 对于二维直线,输出output为4维,前两维代表拟合出的直线的方向,后两位代表直线上的一点。(即通常说的点斜式直线)
# 注意!此代码的 几何中心 有误差!
#--------------------------------------------------------------------#
img = cv2.imread(imgSrc5)
# img = img[ int(0.3*img.shape[0]):int(0.7*img.shape[0]) ,:]
# 可以只截取一部分来拟合,提高精确度
showImg(img)
img_width = img.shape[1]
img_height = img.shape[0]
gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)
thres = cv2.threshold(gray,100,255,cv2.THRESH_BINARY_INV)[1]
element=np.ones((4,4), np.uint8)
thres = cv2.morphologyEx(thres, cv2.MORPH_ERODE, element)
element=np.ones((2,2), np.uint8)
thres=cv2.morphologyEx(thres, cv2.MORPH_DILATE, element)
thres=cv2.morphologyEx(thres, cv2.MORPH_ERODE, element)
v = cv2.Canny(thres, 30, 80)
# 获取点集
points = []
for y in range(img_height):
for x in range(img_width):
if v[y,x]==255 :
points.append([x,y])
points = np.array(points)
output = cv2.fitLine(points, cv2.DIST_L2, 0, 0.01, 0.01)
k = output[1] / output[0]
b = output[3] - k * output[2]
p1 = ( 0 , int(k*(0-output[2])+output[3]) )
p2 = ( int(img_width-1) , int(k*(img_width-1-output[2])+output[3]) ) #
cv2.line(img,p1,p2,(0,0,255),2)
M = cv2.moments(points,True)
cx,cy = int(M['m10']/M['m00']) , int(M['m01']/M['m00'])
cv2.circle(img,(cx,cy),4,(130,170,0),-1,8)
showImg(img)
模板匹配
- res = cv2.matchTemplate(img,template,method)
- img:原图
- template:要匹配的模板
- method
- cv2.TM_SQDIFF:计算平方不同,结果越小,相关性越大
- cv2.TM_CCORR:计算相关性,结果越大,相关性越大
- cv2.TM_CCOEFF:计算相关系数,结果越大,相关性越大
- cv2.TM_SQDIFF_NORMED:计算归一化平方不同
- cv2.TM_CCORR_NORMED:计算归一化相关性
- cv2.TM_CCOEFF_NORMED:计算归一化相关系数
img = cv2.imread(imgSrc4)
tmp = cv2.imread(imgSrc5)
h,w = tmp.shape[:2]
imgGray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
tmpGray = cv2.cvtColor(tmp,cv2.COLOR_BGR2GRAY)
res = cv2.matchTemplate(imgGray,tmpGray,cv2.TM_CCOEFF_NORMED)
# 单个匹配
minval,maxvak,minloc,maxloc = cv2.minMaxLoc(res)
top_left = minloc
bottom_right = (top_left[0]+w,top_left[1]+h)
cv2.rectangle(img,top_left,bottom_right,(0,0,255),2)
showImg(img)
# 多个匹配
threshold = 0.8
loc = np.where(res >= threshold)
for pt in zip(*loc[::-1]):
bottom_right = (pt[0]+w,pt[1]+h)
cv2.rectangle(img , pt , bottom_right, (0,0,255), 1)
showImg(img)
matchTemplate(src,temp,result,CV_TM_CCOEFF_NORMED);
normalize(result,result,0,1,NORM_MINMAX,-1);//归一化到0-1范围
// 单个匹配
minMaxLoc(result,&minValue,&maxValue,&minLoc,&maxLoc);
rectangle(dst,maxLoc,Point(maxLoc.x+temp.cols,maxLoc.y+temp.rows),Scalar(0,255,0),2,8);
// 多个匹配
if(maxValue>=0.7)
rectangle(showImg, maxLoc, Point(maxLoc.x + templateImg.cols, maxLoc.y + templateImg.rows), Scalar(0, 255, 0), 2);
Trackbar
-
方便实时调整参数,找到合适的阈值
-
cv.namedWindow(winName)
- 创建名字为 winName 一个窗口
-
createTrackbar(const String& trackbarname, const String& winname, int *value, int count, TrackbarCallback onChange* = 0, void userdata** = 0)
-
trackbarname:跟踪栏名称,创建的轨迹栏的名称。
-
Winname:窗口的名字,表示这个轨迹条会依附到哪个窗口上,即对应namedWindow()创建窗口时填的窗口名。
-
value:指向整数变量的可选指针,该变量的值反映滑块的初始位置。
-
count:表示滑块可以达到的最大位置的值,最小位置始终为0。
-
onChange:指向每次滑块更改位置时要调用的函数的指针,有默认值0。此函数的原型应为void XXX (int, void *); ,其中第一个参数是轨迹栏位置,第二个参数是用户数据(请参阅下一个参数)。如果回调是NULL指针,则不调用任何回调,而仅更新值。
-
userdata:传递给回调的用户数据,有默认值0。它可以用于处理轨迹栏事件,而无需使用全局变量。如果使用的第三个参数value实参是全局变量的话,完全可以不去管这个userdata参数。
-
-
value= cv2.getTrackbarPos(trackbarName,winName)
- trackbarName:条状进度条的名字
- winName:进度条的名字
- value:得到trackbar当前的值
# 二值化调试
def update(x):
thres = cv2.getTrackbarPos('Threshold','test')
res=cv2.threshold(img,thres,255,cv2.THRESH_BINARY)[1]
cv2.imshow('test',res)
img = cv2.imread(imgSrc1,0)
cv2.namedWindow('test')
thres = 100
cv2.createTrackbar('Threshold','test', thres ,255,update)
update(0)
cv2.waitKey(0)
cv2.destroyAllWindows()
# HSV 调试
def onTrackbar(x):
minh, mins, minv = cv2.getTrackbarPos('minh', 'test'), \
cv2.getTrackbarPos('mins', 'test'), \
cv2.getTrackbarPos('minv', 'test')
maxh, maxs, maxv = cv2.getTrackbarPos('maxh', 'test'), \
cv2.getTrackbarPos('maxs', 'test'), \
cv2.getTrackbarPos('maxv', 'test')
color1 = (minh,mins,minv)
color2 = (maxh,maxs,maxv)
print(color1,color2)
res = cv2.inRange(img, color1, color2)
cv2.imshow('test',res)
orig = cv2.imread(imgSrc4)
img = cv2.cvtColor(orig,cv2.COLOR_BGR2HSV)
showImg(img)
minh,mins,minv = 0,0,0
maxh,maxs,maxv = 255,255,255
color1 = (minh,mins,minv)
color2 = (maxh,maxs,maxv)
cv2.namedWindow('test')
cv2.createTrackbar('minh','test', minh ,255,onTrackbar)
cv2.createTrackbar('mins','test', mins ,255,onTrackbar)
cv2.createTrackbar('minv','test', minv ,255,onTrackbar)
cv2.createTrackbar('maxh','test', maxh ,255,onTrackbar)
cv2.createTrackbar('maxs','test', maxs ,255,onTrackbar)
cv2.createTrackbar('maxv','test', maxv ,255,onTrackbar)
onTrackbar(0)
cv2.waitKey(0)
cv2.destroyAllWindows()
void onChange(int, void* param)
{
Mat img = *(Mat *)param;
Mat dst;
threshold(img,dst,thr,255,THRESH_BINARY);
imshow("test",dst);
}
int main()
{
string src = "img/1.jpg";
Mat img = imread(src,1);
namedWindow("test",WINDOW_NORMAL);
createTrackbar("Threshold","test",&thr,255,onChange,&img);
imshow("test",img);
waitKey(0);
}
绘图
- matplotlib.pyplot模块
plt.plot(x, y, format_string, kwargs)
'''
普通画图
x:x轴数据,列表或数组,可选
y:y轴数据,列表或数组
format_string:控制曲线的格式字符串,可选,由颜色字符、风格字符和标记字符组成。
'''
plt.xlim(x,y)
'''
设置直方图显示的x坐标范围
显示的是x轴的作图范围,右端点为开区间
也可以 赋值列表
'''
plt.hist(
x, bins=10, range=None, normed=False,
weights=None, cumulative=False, bottom=None,
histtype=u'bar', align=u'mid', orientation=u'vertical',
rwidth=None, log=False, color=None, label=None, stacked=False,
hold=None, **kwargs)
'''
画图,可以一次性把各参数设置好
x:数据 ,n维数组
bin:箱子的个数
粗暴的使用就是赋值数据即可
'''
plt.imshow(img,'gray')
'''
在 plt 窗口中显示一张图片
'''
- cv2自带
- cv2.circle(img, center, radius, color[, thickness[, lineType[, shift]]])
- img:输入的图片data
- center:圆心位置
- radius:圆的半径
- color:圆的颜色
- thickness:圆形轮廓的粗细(如果为正)。负厚度表示要绘制实心圆。
- lineType: 圆边界的类型。
- shift:中心坐标和半径值中的小数位数。
- cv2.line(img, pt1, pt2, color[, thickness[, lineType[, shift]]])
- img:源图像
- pt1,pt2:直线的两个端点
- color:需要传入的颜色
- thickness:线条的粗细,默认值是1
- linetype:线条的类型,8 连接,抗锯齿等。默认情况是 8 连接。cv2.LINE_AA 为抗锯齿,这样看起来会非常平滑。
- cv2.rectangle(img, pt1, pt2, color[, thickness[, lineType[, shift]]])
- img:图片
- pt1,pt2:互为对角线的两个端点
- color:线条颜色
- thickness:线条的粗细,默认值是1
- linetype:线条的类型
- shift:
- putText(img, text, org, fontFace, fontScale, color[, thickness[, lineType[, shift]]])
- img:图片
- text:要添加的文字
- org:文字添加到图片上的位置
- fontFace:字体的类型
- cv2.FONT_HERSHEY_SIMPLEX
- fontScale:字体大小
- 推荐1
- color:字体颜色
- thickness:字体粗细
img = cv2.imread(imgSrc0)
cv2.circle(img,(50,50),20,(0,0,255))
cv2.line(img,(30,90),(90,30),(0,255,0))
cv2.rectangle(img,(20,80),(80,300),(255,0,0))
cv2.putText(img,"Hello",(30,30),cv2.FONT_HERSHEY_SIMPLEX,1,(255,255,0));
showImg(img)
circle(img,Point(200,200),100,(0,255,0),5,8);
circle(img,Point(200,200),100,(0,255,0),-1,8);//-1画实心圆
line(img,Point(10,10),Point(100,250),Scalar(0,0,255),5,8);
rectangle(img,Point(300,300),Point(500,600),Scalar(255,0,0),3,8);
rectangle(img,Rect(500,500,100,100),Scalar(0,0,255),3,8);
putText(img,"china",Point(100,100),FONT_HERSHEY_COMPLEX,1,Scalar(0,0,255),1);
直方图
绘制直方图
-
hist = cv2.calcHist(img,channels,mask,histSize,ranges)
- hist:一个 n*1 的二维矩阵
- img
- channels:指定哪个通道 ;img为彩色则为0-2,灰度图为01
- mask:掩码,类似ROI,指定区域进行计算
- histSize:bin的数目,箱子的个数;入[256]表示直方图的箱子数255
- range:告知 的 像素范围 , 直方图的max_y值 ;如[0,256]表示区间[0,256)
img = cv2.imread(imgSrc4,0) showImg(img) hist = cv2.calcHist([img],[0],None,[256],[0,256]) plt.hist(img.ravel(),256) plt.show() histr = cv2.calcHist([img],[0],None,[256],[0,256]) plt.plot(histr,color = 'b') plt.xlim([0,256]) plt.show() -
img.ravel()
- 将img拉成一维数组 ,相当于变成直方图
mask 操作
- cv2.bitwise_and(img1,img2,mask=None)
- 图像位运算
- img1,img2:参与运算的两个图像,可以输入图像或标量,标量可以为单个数值或一个四元组
- mask:掩膜
img = cv2.imread(imgSrc1,0)
mask = np.zeros(img.shape[:2],np.uint8) # 与原图大小保持一致
mask[100:300,100:400] = 255
showImg(mask)
masked_img = cv2.bitwise_and(img,img,mask=mask) # 两个img与运算 然后只输出掩膜部分
showImg(masked_img)
均衡化
- 原理:计算每一种像素值的概率,求出每个像素值的累积概率,再映射到 累积概率*255
- 效果:令图像高亮,使得图像内各组分差异变大;缺点是会丢失一些细节信息
- 技巧:不全局做均衡化,而是局部做均衡化。也就是,自适应均衡化
- equ = cv2.equalizeHist(img)
- 返回一个均匀化后的图像
equ = cv2.equalizeHist(img)
plt.hist(equ.ravel(),256)
plt.show()
自适应均衡化
- cv2.createCLAHA(clipLimit, titleGridSize)
- clipLimit:颜色对比度的阈值
- titleGridSize:进行像素均衡化的网格大小,即在多少网格下进行直方图的均衡化操作
img = cv2.imread(imgSrc1,0)
# 自适应均衡化
clahe = cv2.createCLAHE(clipLimit=2.0,tileGridSize=(8,8)) # 默认设置
res_clahe = clahe.apply(img)
# 全局均衡化
equ = cv2.equalizeHist(img)
res = np.hstack((res_clahe,equ))
showImg(res)
多线程
- 互斥锁 需要threading模块
- lock = threading.Lock()
- 互斥锁对象,全局唯一
- lock.acquire()
- 获取锁。未获取到会阻塞程序,直到获取到锁才会往下执行
- 和release配套使用,否则程序堵塞就寄了
- lock.release()
- 释放锁,归还锁,其他人可以拿去用了
import threading
lock = threading.Lock()
lock.acquire()
v1 = readVideo(0)
lock.release()
lock.acquire() # 拿到锁了才会继续执行 ,拿不到就卡在这里
v2 = readVideo(1)
lock.release()
Zbar
- barcodes = pyzbar.decode(img)
- 对图片进行解码 并直接返回解码内容
- data:二维码存储的信息,注意需要解码为utf-8格式
- type:二维码类型
- rect:二维码外接矩形(x,y,w,h)
- polygon:二维码四点坐标
- quality:
- orientation:二维码方向
- up: 正面
# barcodes内容
barcodes = [Decoded(data=b'Hello Python',
type='QRCODE',
rect=Rect(left=430, top=30, width=340, height=340),
polygon=[Point(x=430, y=30),
Point(x=430, y=368),
Point(x=770, y=370),
Point(x=768, y=30)],
quality=1,
orientation='UP'),
Decoded(data=b'Hello Zbar',
type='QRCODE',
rect=Rect(left=30, top=30, width=340, height=340),
polygon=[Point(x=30, y=30),
Point(x=30, y=368),
Point(x=370, y=370),
Point(x=368, y=30)],
quality=1,
orientation='UP')]
# 示范代码
import pyzbar.pyzbar as pyzbar
img1 = cv2.imread("img/Hello+Zbar.png")
img2 = cv2.imread("img/Hello+Python.png")
img = np.hstack((img1,img2))
showImg(img)
barcodes = pyzbar.decode(img)
for barcode in barcodes:
data = barcode.data.decode("utf-8")
type = barcode.typy
text = "data:{}\ntype:{}".format(data,type)
print(text)
for barcode in barcodes:
points = barcode.polygon # 左上角 左下角 右下角 右上角
k = 5
point0 = (points[0][0]-k,points[0][1]-k)
point1 = (points[1][0]-k,points[1][1]+k)
point2 = (points[2][0]+k,points[2][1]+k)
point3 = (points[3][0]+k,points[3][1]-k)
cv2.circle(img, point0, 2, (0, 0, 255))
cv2.circle(img, point1, 2, (0, 0, 255))
cv2.circle(img, point2, 2, (0, 0, 255))
cv2.circle(img, point3, 2, (0, 0, 255))
showImg(img)
傅里叶变换
- 基本概念
- 高频:变化剧烈的灰度分量,例如边界
- 低频:变化缓慢的灰度分类,例如大海
img = cv2.imread(imgSrc2,0)
showImg(img)
img_float32 = np.float32(img)
dft = cv2.dft(img_float32,flags = cv2.DFT_COMPLEX_OUTPUT)
dft_shift = np.fft.fftshift(dft)
magnitude_spectrum = 20*np.log(cv2.magnitude(dft_shift[:,:,0],dft_shift[:,:,1]))
magnitude_spectrum = (magnitude_spectrum ).astype(np.uint8) # 转换为 图像数据 的存储格式 uint8
showImg(magnitude_spectrum)
plt.imshow(magnitude_spectrum, cmap = 'gray')
plt.show()
- 滤波
- 低通滤波器:只保留低频,会使得图像模糊
- 高通滤波器:只保留高频,会使得图像细节增强
img = cv2.imread(imgSrc2,0)
showImg(img)
img_float32 = np.float32(img)
# 傅里叶变换
# cv2.dft() 返回的结果是双通道的 ( 实部,虚部 ),通常还需要转换成图像格式才能展示(0,255)像素值
dft = cv2.dft(img_float32,flags = cv2.DFT_COMPLEX_OUTPUT)
dft_shift = np.fft.fftshift(dft)
rows,cols = img.shape
crow,ccol = int(rows/2),int(cols/2) # 得到中心位置
# 低通滤波
mask_low = np.zeros((rows,cols,2),np.uint8) # 注意是三维的!
mask_low[crow-30:crow+30,ccol-30:ccol+30]=1 # 只保留中心周围的点
# 高通滤波
mask_high = np.ones((rows,cols,2),np.uint8) # 注意是三维的!
mask_high[crow-30:crow+30,ccol-30:ccol+30]=0 # 只保留中心周围的点
mask = mask_low # 选择滤波器
# 傅里叶逆变换
fshift = dft_shift * mask # mask 的取值是0或1
f_ishift = np.fft.ifftshift(fshift) # 将中心位置的频谱还原
img_back = cv2.idft(f_ishift)
img_back = cv2.magnitude(img_back[:,:,0],img_back[:,:,1])# 还需要双通道合并
# img_back = 20*np.log(img_back)
# 对两个通道进行转换才能得到图像形式表达,由于转换后的值为非常小的数值,因此还要转换到 0-255 之间
plt.subplot(121),plt.imshow(img,cmap = 'gray')
plt.title('Input Image'), plt.xticks([]),plt.yticks([])
plt.subplot(122),plt.imshow(img_back,cmap='gray')
plt.title('Result'),plt.xticks([]),plt.yticks([])
plt.show()
showImg(img_back)
角点检测
- cornerHarris更集成化,操作简单,但自由度低;速度快效果一般
- goodFeaturesToTrack参数复杂,但是更自由
- dst = cv2.cornerHarris(img,blockSize,ksize,k)
- 数学原理:
死去的数论开始攻击我,再加一个此时此刻劳资没学线代,白给三个小时 - dst:处理后得到的每个像素点的自相似性,与原图大小一致的矩阵; 一般认为 *dst>0.01dst.max() 为角点,系数越大越严格**
- img:数据类型为float32的图像
- blockSize:移动窗口的大小
- ksize:sobel算子的大小
- k:取值参数为[0.04,0.06],一般取 0.04 ;原因涉及数学原理,开摆!
- 数学原理:
img = cv2.imread(imgSrc3)
showImg(img)
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
dst = cv2.cornerHarris(gray,2,3,0.04)
img[dst>0.09*dst.max()] = (0,0,255)
showImg(img)
- corners=cv.goodFeaturesToTrack(image, maxCorners, qualityLevel, minDistance, mask, blockSize, gradientSize[, corners[, useHarrisDetector[, k]]])
- image: 输入图像,是八位的或者32位浮点型,单通道图像,所以有时候用灰度图
- maxCorners: 返回最大的角点数,是最有可能的角点数,如果这个参数不大于0,那么表示没有角点数的限制
- qualityLevel: 图像角点的最小可接受参数,质量测量值乘以这个参数就是最小特征值,小于这个数的会被抛弃
- minDistance: 返回的角点之间最小的欧式距离
- mask: 检测区域。如果图像不是空的(它需要具有CV_8UC1类型和与图像相同的大小),它指定检测角的区域
- blockSize: 用于计算每个像素邻域上的导数协变矩阵的平均块的大小
- useHarrisDetector:选择是否采用Harris角点检测,默认是false
- k: Harris检测的自由参数
霍夫检测
- lines = cv2.HoughLines(image_edge, rho, theta, threshold[, lines[, srn[, stn[, min_theta[, max_theta]]]]])
- image_edge:二值图;之所谓为edge是因为最好经过边缘或阈值处理
- rho:像素之间的距离
- theta:直线角度范围,2pi/(pi/180) = 360°
- threshold:一条预选直线上的最少像素点个数,不满足会被筛出
- lines:(rho, theta)的列表
- 如果距离是1,180个像素即可生成直线,如果距离是2,至少360个像素才可以生成直线
- 原理详见,(写得极好!) 图解cv2.HoughLines() 和 cv2.HoughLinesP()原理和代码
- 霍夫逆变换 : 将(rho,theta)转化为(x,y)
- a = cos(theta)
- b = sin(theta)
- x0 = a rho , y0 = b rho
- x1 = x0 + 1000 (-b) , y1 = y0 + 1000 a
- x2 = x0 - 1000 (-b) , y2 = y0 -1000 a
# 霍夫线检测
orig = cv2.imread(imgSrc4)
img = cv2.cvtColor(orig,cv2.COLOR_BGR2HSV)
minh , mins , minv = 22 ,10,22
maxh , maxs , maxv = 136,150,121
img = cv2.inRange(img,(minh,mins,minv),(maxh,maxs,maxv))
element = cv2.getStructuringElement(cv2.MORPH_RECT, (3,6))
img = cv2.morphologyEx(img, cv2.MORPH_ERODE, element)
element = cv2.getStructuringElement(cv2.MORPH_RECT, (2,2))
img = cv2.morphologyEx(img, cv2.MORPH_DILATE, element)
img = cv2.morphologyEx(img, cv2.MORPH_ERODE, element)
after_canny = cv2.Canny(img,100,200,3)
showImg(after_canny)
lines = cv2.HoughLines(after_canny, 1, np.pi/180, 160)
for line in lines:
rho , theta = line[0][0] , line[0][1] # line 是一个数组的数组
a , b = np.cos(theta) , np.sin(theta)
x0 , y0 = a*rho , b*rho
point1 = ( int(x0+1000*(-b)) , int(y0+1000*a) )
point2 = ( int(x0-1000*(-b)) , int(y0-1000*a) )
cv2.line(orig,point1,point2,(55,100,195),2,8)
showImg(orig)
vector<Vec2f> lines; //定义矢量结构lines用于存放得到的线段矢量集合
HoughLines(src, lines, 1, CV_PI/180, 150);//超过150的线段才被检测到
//依次在图中绘制出每条线段
for (size_t i = 0; i < lines.size(); i++)
{
float rho=lines[i][0],theta=lines[i][1];
Point pt1,pt2;
double a=cos(theta),b=sin(theta);
double x0=a*rho,y0=b*rho;
pt1.x=cvRound(x0+1000*(-b));//cvRound(double value) 函数:对一个double型数字四舍五入,返回一个整数
pt1.y=cvRound(y0+1000*(a));
pt2.x = cvRound(x0 - 1000*(-b));
pt2.y = cvRound(y0 - 1000*(a));
line(dst,pt1,pt2,Scalar(55,100,195),2,8);
}
- circles = cv2.HoughCircles(image, method, dp, minDist, [circles=None], param1=None, param2=None, minRadius=None, maxRadius=None)
- image 原始图像
- method 目前只支持cv2.HOUGH_GRADIENT
- dp 图像解析的反向比例。1为原始大小,2为原始大小的一半
- minDist 圆心之间的最小距离。过小会增加圆的误判,过大会丢失存在的圆
- param1 Canny检测器的高阈值 ,
所以似乎是自带一个Canny? - param2 检测阶段圆心的累加器阈值。越小的话,会增加不存在的圆;越大的话,则检测到的圆就更加接近完美的圆形
- minRadius 检测的最小圆的半径,就是检测到的圆半径取值为[minRadius,maxRadius]
- maxRadius 检测的最大圆的半径
- circles 和霍夫线一样是一个float数组的数组,例如 [[306.5,222.5,193.4]]
# 霍夫圆检测
orig = cv2.imread(imgSrc1)
img = cv2.GaussianBlur(orig,(7,7),2)
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
thres = cv2.threshold(gray, 25, 255, cv2.THRESH_BINARY)[1]
thres = cv2.GaussianBlur(thres,(7,7),2)
circles = cv2.HoughCircles(thres,cv2.HOUGH_GRADIENT,1,20,100,80,50,0)
for circle in circles:
center = ( int(circle[0][0]) , int(circle[0][1]) )
radius = int(circle[0][2])
cv2.circle(orig,center,radius,(0,0,255),2)
showImg(orig)
vector<Vec3f> circles;
HoughCircles(src, circles, CV_HOUGH_GRADIENT,1.5, 10, 200, 100);
for(size_t i = 0; i<circles.size(); i++)
{
Point center(cvRound(circles[i][0]), cvRound(circles[i][1]));
int radius = cvRound(circles[i][2]);
circle(dst, center, 3, Scalar(0, 0, 255), -1, 8,0);//设置为-1时,画实心圆
circle(dst, center, radius, Scalar(0, 255, 0), 3, 8,0);//画空心圆
}
尺度空间
- void
特征匹配
- void
图像金字塔
高斯金字塔
- 向下取样法(变小)
up = cv2.pyrUp(img)
- 向上取样法(变大)
up = cv2.pyrUp(img)
拉普拉斯金字塔
- L = G - PyrUp( PyrDown(G) )
- = 原图 - 向下在向上
img = cv2.imread(imgSrc1,1)
down = cv2.pyrDown(img)
up = cv2.pyrUp(down)
l = img-up
res = np.hstack((img,l))
showImg(res)