赞
踩
提取函数(将图像分为三个通道,可通过索引获取对应通道的图像):cv2.split(img)
合并函数:cv2.merge((b,g,r))
获取矩阵/数组维数:.shape()
当图片四周边界有空白(或者扩充边界),可以用边界填充方式进行填充。
传入原图,定义好整个图像的大小(四个大小),选定填充方法。
replicate = cv2.copyMakeBorder(img, top_size, bottom_size, left_size, right_size, borderType=cv2.BORDER_REPLICATE)
如图,图一为原图,其余是不同填充方式得到的结果。
居中的图片:
进行图像融合时一定要将两张图片大小调整一致。
通过.shape()函数获其中一个图像的大小,另一张图像用resize函数调整大小,addWeighted()函数进行图像融合。
关键代码:
img_cat.shape
img_dog = cv2.resize(img_dog, (500, 414))
res = cv2.addWeighted(img_cat, 0.4, img_dog, 0.6, 0)
效果展示:
融合结果:
基础:膨胀、腐蚀操作。
开运算:腐蚀后膨胀;闭运算:膨胀后腐蚀。
当图片中有细小毛躁,可用腐蚀操作去掉,此时图片会有损毁,再用膨胀操作尽力恢复。
礼帽:原始输入-开运算;黑帽:闭运算-原始输入。
梯度:膨胀-腐蚀。Sobel算子、scharr算子。
Scharr算子对差异更敏感,噪音点更容易被保留。
原图:
开运算:
#创建一个五行五列无符号整数类型的数组,kernel的大小形状决定腐蚀或膨胀操作的强度
kernel=np.ones((5,5),np.uint8)
#kernel的大小形状决定腐蚀或膨胀操作的强度
opening=cv2.morphologyEx(img,cv2.MORPH_OPEN,kernel)
闭运算:
kernel=np.ones((5,5),np.uint8)
closing=cv2.morphologyEx(img,cv2.MORPH_CLOSE,kernel)
礼帽:
tophat=cv2.morphologyEx(img,cv2.MORPH_TOPHAT,kernel)
黑帽:
blackhat=cv2.morphologyEx(img,cv2.MORPH_BLACKHAT,kernel)
梯度:膨胀操作-腐蚀操作,梯度
kernel = np.ones((7,7),np.uint8)
gradient = cv2.morphologyEx(pie, cv2.MORPH_GRADIENT, kernel)
Sobel算子:经过绝对值操作,否则两边只能保存一边(正数部分),负数部分会被截断。
sobelx = cv2.Sobel(img,cv2.CV_64F,1,0,ksize=3)
sobelx = cv2.convertScaleAbs(sobelx)
Sobel算子和scharr算子对比:
#图像梯度-sobel算子 #计算图像的梯度(相当于检测边缘) #分为x,y方向轴,最后整合 #给出的都是关键代码(省略读取、显示) # ...,图像深度,1,0代表对水平方向进行处理,sobel算子大小 #x sobelx=cv2.Sobel(img,cv2.CV_64F,1,0,ksize=3) #y sobely=cv2.Sobel(img,cv2.CV_64F,0,1,ksize=3) ##上面两个结果为什么会显示一半?因为在进行运算的时候, ##关于水平方向右边是黑减白,得到的值是负数;关于竖直方向下边也是黑减白,得负数。 ##负数都会被截断成0,,为了解决这个问题,我们要取绝对值 ##ps:1.中间部分逐渐趋向黑色就是趋近于0;2.不建议直接将水平+竖直两个方向一块计算,会更模糊 #x水平方向为例 sobelx = cv2.Sobel(img,cv2.CV_64F,1,0,ksize=3) sobelx = cv2.convertScaleAbs(sobelx) ##求和(将水平、竖直两个方向相加) sobelxy=cv2.addWeighted(sobelx,0.5,sobely,0.5,0) cv_show(sobelxy,'sobelsy')
##图像梯度--scharr算子(结果差异更明,对差异更敏感) ##图像梯度--laplacian算子(二阶导:体现一阶导的变换,更敏感,对噪音点敏感, ##!但噪音点不一定是边界) #三种方法对比 #sobel算子 img=cv2.imread('lena.jpg',cv2.IMREAD_GRAYSCALE) sobelx=cv2.Sobel(img,cv2.CV_64F,1,0,ksize=3) sobely=cv2.Sobel(img,cv2.CV_64F,0,1,ksize=3) sobelx=cv2.convertScaleAbs(sobelx) sobely=cv2.convertScaleAbs(sobely) sobelxy=cv2.addWeighted(sobelx,0.5,sobely,0.5,0) #scharr算子 scharrx=cv2.Scharr(img,cv2.CV_64F,1,0) scharry=cv2.Scharr(img,cv2.CV_64F,0,1) scharrx=cv2.convertScaleAbs(scharrx) scharry=cv2.convertScaleAbs(scharry) scharrxy=cv2.addWeighted(scharrx,0.5,scharry,0.5,0) ##laplacian laplacian=cv2.Laplacian(img,cv2.CV_64F) laplacian=cv2.convertScaleAbs(laplacian) res=np.hstack((sobelxy,scharrxy,laplacian)) cv_show(res,'res')
为解决噪音点问题。
原图:
常用滤波:均值滤波、方框滤波、高斯滤波、中值滤波。
均值滤波:顾名思义就是取像素点求和取平均值。
blur=cv2.blur(img,(3,3))
方框滤波:当参数normallize为TRUE时,结果和均值一样;为False时,超过255的部分当作255处理,显示白色。
box = cv2.boxFilter(img,-1,(3,3), normalize=True)
高斯滤波:高斯模糊的卷积核里的数值满足高斯分布,相当于更重视中间的(矩阵)xy方向标准差都是1。
aussian=cv2.GaussianBlur(img,(5,5),1)
中值滤波:相当于用中值代替(更加平滑了甚至有点模糊)。
median=cv2.medianBlur(img,5)
给图像找一个上下边界,边界内称为阈值,根据type对图像处理。
ret, dst = cv2.threshold(src, thresh, maxval, type)
src: 输入图,只能输入单通道图像,通常来说为灰度图
dst: 输出图
thresh: 阈值
maxval: 当像素值超过了阈值(或者小于阈值,根据type来决定),所赋予的值
type:二值化操作的类型,包含以下5种类型: cv2.THRESH_BINARY; cv2.THRESH_BINARY_INV; cv2.THRESH_TRUNC; cv2.THRESH_TOZERO;cv2.THRESH_TOZERO_INV
cv2.THRESH_BINARY 超过阈值部分取maxval(最大值),否则取0
cv2.THRESH_BINARY_INV THRESH_BINARY的反转
cv2.THRESH_TRUNC 大于阈值部分设为阈值,否则不变
cv2.THRESH_TOZERO 大于阈值部分不改变,否则设为0
cv2.THRESH_TOZERO_INV THRESH_TOZERO的反转
结果:
需要先处理为灰度图。
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)
#通过索引获取其中某个轮廓
contours[0]
#轮廓绘制:
#函数中的参数:传入绘制图像,轮廓,轮廓索引#retr_tree(-1代表所有),颜色模式(三个通道),#线条厚度
res=cv2.drawContours(draw_img,contours,-1,(0,0,255),2)
计算轮廓面积、周长
cv2.contourArea(cnt)
cv2.arcLength(cnt,True)
#绘制边界矩形
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)
cnt=contours[0]
x,y,w,h=cv2.boundingRect(cnt)
img=cv2.rectangle(img,(x,y),(x+w,y+h),(0,255,0),2)
以边界矩形为例,结果如图:
高斯金字塔
向下采样,缩小;向上采样,放大。
两个操作先后执行仍然会有精度的缺失。
原图:
向上采样:
up=cv2.pyrUp(img)
向下采样:
down=cv2.pyrDown(img)
拉普拉斯金字塔
为实现高斯金字塔图像重建,将高斯金字塔与其上一层通过采样扩大后的差值图像。
down=cv2.pyrDown(img)
down_up=cv2.pyrUp(down)
l_1=img-down_up
统计不同像素值在一幅图像中的个数,并以直方图的方式显示。
hist = cv2.calcHist([img],[0],None,[256],[0,256])
要处理的图像(需要先灰度处理):
统计所得直方图:
cv2.calcHist(images,channels,mask,histSize,ranges)
- images: 原图像图像格式为 uint8 或 float32。当传入函数时应 用中括号 [] 括来例如[img]
- channels: 同样用中括号括来它会告函数我们统幅图 像的直方图。如果入图像是灰度图它的值就是 [0]如果是彩色图像 的传入的参数可以是 [0][1][2] 它们分别对应着 BGR。
- mask: 掩模图像。统整幅图像的直方图就把它为 None。但是如 果你想统图像某一分的直方图的你就制作一个掩模图像并 使用它。
- histSize:BIN 的数目。也应用中括号括来
- ranges: 像素值范围常为 [0256]
1.1直方图均衡化
可以增强局部的对比度,但不影响整体的对比度。
equ = cv2.equalizeHist(img)
1.2自适应直方图均衡化
第一个参数控制对比度,第二个参数确定被分割的区域,若图像被分割的区域越多,图像就越平滑。Apply方法将图像的对比度进行拉伸。
clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8,8))
res_clahe = clahe.apply(img)
结果图:
填充选中区域(mask),可以过滤掉不需要的部分(与操作)。
创建mask
#mask填充的图
mask = np.zeros(img.shape[:2], np.uint8)
#与mask图与操作后的图
masked_img = cv2.bitwise_and(img, img, mask=mask)#与操作
模板匹配和卷积原理很像,模板在原图像上从原点开始滑动,计算模板与(图像被模板覆盖的地方)的差别程度。
#第二个参数0,表示取为灰度图像
img=cv2.imread('lena.jpg',0)
#五种模板匹配的方法:
#- TM_SQDIFF:计算平方不同,计算出来的值越小,越相关
#- TM_CCORR:计算相关性,计算出来的值越大,越相关
#- TM_CCOEFF:计算相关系数,计算出来的值越大,越相关
#- TM_SQDIFF_NORMED:计算归一化平方不同,计算出来的值越接近0,越相关
#- TM_CCORR_NORMED:计算归一化相关性,计算出来的值越接近1,越相关
#- TM_CCOEFF_NORMED:计算归一化相关系数,计算出来的值越接近1,越相关
#在函数matchTemplate第三个参数中填入所选的参数方法:
res = cv2.matchTemplate(img, template, cv2.TM_SQDIFF)
#通过函数minMaxLoc()获取矩阵中的最大最小值以及它们的位置,后续可以利用得到的位置绘制所匹配到的位置的矩形框。
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res)
给出两个方法的结果。
匹配多个对象:
res = cv2.matchTemplate(img_gray, template, cv2.TM_CCOEFF_NORMED)
threshold = 0.8
# 取匹配程度大于%80的坐标
loc = np.where(res >= threshold)
for pt in zip(*loc[::-1]): # *号表示可选参数
bottom_right = (pt[0] + w, pt[1] + h)
cv2.rectangle(img_rgb, pt, bottom_right, (0, 0, 255), 2)
结果:要匹配的图像是金币。
# 实例化,绘制关键点
sift = cv2.xfeatures2d.SIFT_create()
kp = sift.detect(gray, None)
img=cv2.drawKeypoints(gray,kp,img)
1.读取模板图像
2.将模板图像中的对应数字图像分割对应上表示的数字,通过数据结构元组表示
2.1(2具体)灰度处理–二值图像(边缘明显)(一般情况下,做轮廓检测输入的都是二值图像)–计算轮廓
3.读取银行卡图像
4.将亮的部分处理得更亮,而后得到一组一组(一个长方形条)的四位银行卡号
4.1预处理–灰度处理–礼帽操作–梯度–绝对值+归一化(归一化使图像像素值在一个可控的范围,方便二值操作)
4.2闭运算(先膨胀载腐蚀,将数字连接在一起)–闭运算(填补空缺)–计算轮廓
5.循环处理每一组长方形,将长方形中的四位数字分割开,与模板图像元组对应进行对比,得到相对应的数字
需要识别的银行卡:
结果:
代码:轮廓提取,裁剪每一位卡号,匹配:
# 符合区域大小的留下来 for(i,c) in enumerate(cnts): (x,y,w,h)=cv2.boundingRect(c) ar=w/float(h) if ar>2.5 and ar<4.0: if(w>40 and w<55) and (h>10 and h<20): locs.append((x,y,w,h)) locs=sorted(locs,key=lambda x:x[0]) output=[] for(i,(gX,gY,gW,gH)) in enumerate(locs): groupOutput=[] # 随便取,往外扩一点区域 group = gray[gY - 5:gY + gH + 5, gX - 5:gX + gW + 5] cv_show('group',group) group = cv2.threshold(group, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1] cv_show('group',group) digitCnts,hierarchy = cv2.findContours(group.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) digitCnts = contours.sort_contours(digitCnts,method="left-to-right")[0] for c in digitCnts: # 计算点集或轮廓形成的最小外接矩形 (x,y,w,h)=cv2.boundingRect(c) roi=group[y:y+h,x:x+w] roi=cv2.resize(roi,(57,88)) cv_show('roi',roi) scores=[] # 计算得分,找到数字,匹配 for (digit,digitROI) in digits.items(): result=cv2.matchTemplate(roi,digitROI,cv2.TM_CCOEFF) (_,score,_,_)=cv2.minMaxLoc(result) scores.append(score) groupOutput.append(str(np.argmax(scores))) cv2.rectangle(image, (gX - 5, gY - 5), (gX + gW + 5, gY + gH + 5), (0, 0, 255), 1) cv2.putText(image, "".join(groupOutput), (gX, gY - 15), cv2.FONT_HERSHEY_SIMPLEX, 0.65, (0, 0, 255), 2) output.extend(groupOutput)
预处理–遍历轮廓–透视变换–二值处理–利用OCR进行文字识别。
难点:
透视变换。
#透视变换 def order_points(pts): # 一共4个坐标点 rect = np.zeros((4, 2), dtype = "float32") # 按顺序找到对应坐标0123分别是 左上,右上,右下,左下 # 计算左上,右下 s = pts.sum(axis = 1) rect[0] = pts[np.argmin(s)] rect[2] = pts[np.argmax(s)] # 计算右上和左下 diff = np.diff(pts, axis = 1) rect[1] = pts[np.argmin(diff)] rect[3] = pts[np.argmax(diff)] return rect def four_point_transform(image, pts): # 获取输入坐标点 rect = order_points(pts) (tl, tr, br, bl) = rect # 计算输入的w和h值 widthA = np.sqrt(((br[0] - bl[0]) ** 2) + ((br[1] - bl[1]) ** 2)) widthB = np.sqrt(((tr[0] - tl[0]) ** 2) + ((tr[1] - tl[1]) ** 2)) maxWidth = max(int(widthA), int(widthB)) heightA = np.sqrt(((tr[0] - br[0]) ** 2) + ((tr[1] - br[1]) ** 2)) heightB = np.sqrt(((tl[0] - bl[0]) ** 2) + ((tl[1] - bl[1]) ** 2)) maxHeight = max(int(heightA), int(heightB)) # 变换后对应坐标位置 dst = np.array([ [0, 0], [maxWidth - 1, 0], [maxWidth - 1, maxHeight - 1], [0, maxHeight - 1]], dtype = "float32") # 计算变换矩阵 M = cv2.getPerspectiveTransform(rect, dst) warped = cv2.warpPerspective(image, M, (maxWidth, maxHeight)) # 返回变换后结果 return warped
要处理的图:
图像处理后:
得到的文本结果:
3.特征匹配
预处理–特征点检测–特征点匹配–显示匹配结果
需要匹配的图:
匹配结果:
kp1, des1 = sift.detectAndCompute(img1, None)
kp2, des2 = sift.detectAndCompute(img2, None)
# crossCheck表示两个特征点要互相匹,例如A中的第i个特征点与B中的第j个特征点最近的,并且B中的第j个特征点到A中的第i个特征点也是
#NORM_L2: 归一化数组的(欧几里德距离),如果其他特征计算方法需要考虑不同的匹配计算方式
bf = cv2.BFMatcher(crossCheck=True)
matches = bf.match(des1, des2)
# 根据距离排序。key定义了排序的依据--distance
matches = sorted(matches, key=lambda x: x.distance)
# 前十个关键点
img3 = cv2.drawMatches(img1, kp1, img2, kp2, matches[:10], None,flags=2)
k对最佳匹配,一个点对应两个最近的点(匹配的点更多了):
bf = cv2.BFMatcher()
# 一个点对应两个最近的
matches = bf.knnMatch(des1, des2, k=2)
#保留距离小于0.75的
good = []
for m, n in matches:
if m.distance < 0.75 * n.distance:
good.append([m])
# good:保留全部关键点(倒数第二个参数决定保存的关键点个数)
img3 = cv2.drawMatchesKnn(img1,kp1,img2,kp2,good,None,flags=2)
4.图像拼接
需要拼接的图像:
结果:
关键代码,图像拼接:stitcher
class Stitcher: #拼接函数 def stitch(self, images, ratio=0.75, reprojThresh=4.0,showMatches=False): #获取输入图片 (imageB, imageA) = images #检测A、B图片的SIFT关键特征点,并计算特征描述子 (kpsA, featuresA) = self.detectAndDescribe(imageA) (kpsB, featuresB) = self.detectAndDescribe(imageB) # 匹配两张图片的所有特征点,返回匹配结果 M = self.matchKeypoints(kpsA, kpsB, featuresA, featuresB, ratio, reprojThresh) # 如果返回结果为空,没有匹配成功的特征点,退出算法 if M is None: return None # 否则,提取匹配结果 # H是3x3视角变换矩阵 #只需要知道可以通过一个M矩阵将A图片形状变换为B图片即可,目前不需要知道原理 (matches, H, status) = M # 将图片A进行视角变换,result是变换后图片 result = cv2.warpPerspective(imageA, H, (imageA.shape[1] + imageB.shape[1], imageA.shape[0])) self.cv_show('result', result) # 将图片B传入result图片最左端,采用切片 result[0:imageB.shape[0], 0:imageB.shape[1]] = imageB self.cv_show('result', result) # 检测是否需要显示图片匹配 if showMatches: # 生成匹配图片,特征点连线图 vis = self.drawMatches(imageA, imageB, kpsA, kpsB, matches, status) # 返回结果 return (result, vis) # 返回匹配结果 return result # 建立SIFT生成器 descriptor = cv2.xfeatures2d.SIFT_create() # 检测SIFT特征点,并计算描述子 (kps, features) = descriptor.detectAndCompute(image, None) # 将结果转换成NumPy数组 kps = np.float32([kp.pt for kp in kps]) # 返回特征点集,及对应的描述特征 return (kps, features) def matchKeypoints(self, kpsA, kpsB, featuresA, featuresB, ratio, reprojThresh): # 建立暴力匹配器 matcher = cv2.BFMatcher() # 使用KNN检测来自A、B图的SIFT特征匹配对,K=2 rawMatches = matcher.knnMatch(featuresA, featuresB, 2) matches = [] for m in rawMatches: # 当最近距离跟次近距离的比值小于ratio值时,保留此匹配对 if len(m) == 2 and m[0].distance < m[1].distance * ratio: # 存储两个点在featuresA, featuresB中的索引值 matches.append((m[0].trainIdx, m[0].queryIdx)) # 当筛选后的匹配对大于4时,计算视角变换矩阵 if len(matches) > 4: # 获取匹配对的点坐标 ptsA = np.float32([kpsA[i] for (_, i) in matches]) ptsB = np.float32([kpsB[i] for (i, _) in matches]) # 计算视角变换矩阵 (H, status) = cv2.findHomography(ptsA, ptsB, cv2.RANSAC, reprojThresh) # 返回结果 return (matches, H, status) # 如果匹配对小于4时,返回None return None def drawMatches(self, imageA, imageB, kpsA, kpsB, matches, status): # 初始化可视化图片,将A、B图左右连接到一起 (hA, wA) = imageA.shape[:2] (hB, wB) = imageB.shape[:2] vis = np.zeros((max(hA, hB), wA + wB, 3), dtype="uint8") vis[0:hA, 0:wA] = imageA vis[0:hB, wA:] = imageB # 联合遍历,画出匹配对 for ((trainIdx, queryIdx), s) in zip(matches, status): # 当点对匹配成功时,画到可视化图上 if s == 1: # 画出匹配对 ptA = (int(kpsA[queryIdx][0]), int(kpsA[queryIdx][1])) ptB = (int(kpsB[trainIdx][0]) + wA, int(kpsB[trainIdx][1])) cv2.line(vis, ptA, ptB, (0, 255, 0), 1) # 返回可视化结果 return vis
5.停车场车位识别
大步骤:将图像预处理,选出目标区域–训练训练集,用该算法识别目标区域中的停车场空位。
–读取图像
–得到图像的二值(两种方法,一种canny边缘检测/一种阈值(二值)),也叫背景过滤
–灰度
–边缘(灰度图相对于二值图得到的图像边缘效果更好)
–剔除无用的区域:获取关键点(手动调整标记),将关键点相连(利用边缘点获取的方式吗),mask填充(对选定的图像进行遮挡),剔除像素值不为255的地方。
–获得停车位:霍夫变换,获得横线,调整横线,过滤不符合要求的线(如斜线)。注意特殊列(第一和最后),数字标列(字典表示),调整,将图片分割出来保存(利用xy的坐标及车位之间的距离估计)(难,有关数学)。
–训练集训练得到结果并应用:标记图中的空余车位及计算两种车位的总数。
原图:
车位标识:
class Parking: def cv_show(self,name,img): cv2.imshow(name, img) cv2.waitKey(0) cv2.destroyAllWindows() # △ def show_images(self, images, cmap=None): cols = 2 rows = (len(images)+1)//cols plt.figure(figsize=(15, 12)) for i, image in enumerate(images): plt.subplot(rows, cols, i+1) cmap = 'gray' if len(image.shape)==2 else cmap plt.imshow(image, cmap=cmap) plt.xticks([]) plt.yticks([]) plt.tight_layout(pad=0, h_pad=0, w_pad=0) plt.show() def select_rgb_white_yellow(self,image): #过滤掉背景(只是过滤背景,并没有把无关的部分去掉,取边缘) lower = np.uint8([120, 120, 120]) upper = np.uint8([255, 255, 255]) # lower_red和高于upper_red的部分分别变成0,lower_red~upper_red之间的值变成255,相当于过滤背景 white_mask = cv2.inRange(image, lower, upper) self.cv_show('white_mask',white_mask) masked = cv2.bitwise_and(image, image, mask = white_mask) self.cv_show('masked',masked) return masked def convert_gray_scale(self,image): return cv2.cvtColor(image, cv2.COLOR_RGB2GRAY) def detect_edges(self,image, low_threshold=50, high_threshold=200): return cv2.Canny(image, low_threshold, high_threshold) # 剔除操作,只保存像素值为255的地方 def filter_region(self,image, vertices): """ 剔除掉不需要的地方 """ mask = np.zeros_like(image) if len(mask.shape)==2: cv2.fillPoly(mask, vertices, 255) self.cv_show('mask', mask) return cv2.bitwise_and(image, mask) def select_region(self,image): """ 手动选择区域 """ # first, define the polygon by vertices rows, cols = image.shape[:2] pt_1 = [cols*0.05, rows*0.90] pt_2 = [cols*0.05, rows*0.70] pt_3 = [cols*0.30, rows*0.55] pt_4 = [cols*0.6, rows*0.15] pt_5 = [cols*0.90, rows*0.15] pt_6 = [cols*0.90, rows*0.90] vertices = np.array([[pt_1, pt_2, pt_3, pt_4, pt_5, pt_6]], dtype=np.int32) point_img = image.copy() point_img = cv2.cvtColor(point_img, cv2.COLOR_GRAY2RGB) for point in vertices[0]: cv2.circle(point_img, (point[0],point[1]), 10, (0,0,255), 4) self.cv_show('point_img',point_img) return self.filter_region(image, vertices) def hough_lines(self,image): #输入的图像需要是边缘检测后的结果 #minLineLengh(线的最短长度,比这个短的都被忽略)和MaxLineCap(两条直线之间的最大间隔,小于此值,认为是一条直线) #rho距离精度,theta角度精度,threshod超过设定阈值才被检测出线段 return cv2.HoughLinesP(image, rho=0.1, theta=np.pi/10, threshold=15, minLineLength=9, maxLineGap=4) def draw_lines(self,image, lines, color=[255, 0, 0], thickness=2, make_copy=True): # 过滤霍夫变换检测到直线(通过线与线之间xy坐标的差值,过滤斜线等不符合要求的线) if make_copy: image = np.copy(image) cleaned = [] for line in lines: for x1,y1,x2,y2 in line: if abs(y2-y1) <=1 and abs(x2-x1) >=25 and abs(x2-x1) <= 55: cleaned.append((x1,y1,x2,y2)) cv2.line(image, (x1, y1), (x2, y2), color, thickness) print(" No lines detected: ", len(cleaned)) return image def identify_blocks(self,image, lines, make_copy=True): if make_copy: new_image = np.copy(image) #Step 1: 过滤部分直线 cleaned = [] for line in lines: for x1,y1,x2,y2 in line: if abs(y2-y1) <=1 and abs(x2-x1) >=25 and abs(x2-x1) <= 55: cleaned.append((x1,y1,x2,y2)) #Step 2: 对直线按照x1进行排序 import operator list1 = sorted(cleaned, key=operator.itemgetter(0, 1)) #Step 3: 找到多个列,相当于每列是一排车 # cluster簇,代表一排车,作用为给车位标号 clusters = {} dIndex = 0 clus_dist = 10 # 距离较近,为一簇,距离较远则不是一簇 for i in range(len(list1) - 1): distance = abs(list1[i+1][0] - list1[i][0]) if distance <= clus_dist: if not dIndex in clusters.keys(): clusters[dIndex] = [] clusters[dIndex].append(list1[i]) clusters[dIndex].append(list1[i + 1]) else: dIndex += 1 #Step 4: 得到坐标 rects = {} i = 0 for key in clusters: all_list = clusters[key] cleaned = list(set(all_list)) if len(cleaned) > 5: cleaned = sorted(cleaned, key=lambda tup: tup[1]) avg_y1 = cleaned[0][1] avg_y2 = cleaned[-1][1] avg_x1 = 0 avg_x2 = 0 for tup in cleaned: avg_x1 += tup[0] avg_x2 += tup[2] avg_x1 = avg_x1/len(cleaned) avg_x2 = avg_x2/len(cleaned) rects[i] = (avg_x1, avg_y1, avg_x2, avg_y2) i += 1 print("Num Parking Lanes: ", len(rects)) #Step 5: 把列矩形画出来 buff = 7 for key in rects: tup_topLeft = (int(rects[key][0] - buff), int(rects[key][1])) tup_botRight = (int(rects[key][2] + buff), int(rects[key][3])) cv2.rectangle(new_image, tup_topLeft,tup_botRight,(0,255,0),3) return new_image, rects def draw_parking(self,image, rects, make_copy = True, color=[255, 0, 0], thickness=2, save = True): if make_copy: new_image = np.copy(image) gap = 15.5 spot_dict = {} # 字典:一个车位对应一个位置 tot_spots = 0 #微调 adj_y1 = {0: 20, 1:-10, 2:0, 3:-11, 4:28, 5:5, 6:-15, 7:-15, 8:-10, 9:-30, 10:9, 11:-32} adj_y2 = {0: 30, 1: 50, 2:15, 3:10, 4:-15, 5:15, 6:15, 7:-20, 8:15, 9:15, 10:0, 11:30} adj_x1 = {0: -8, 1:-15, 2:-15, 3:-15, 4:-15, 5:-15, 6:-15, 7:-15, 8:-10, 9:-10, 10:-10, 11:0} adj_x2 = {0: 0, 1: 15, 2:15, 3:15, 4:15, 5:15, 6:15, 7:15, 8:10, 9:10, 10:10, 11:0} for key in rects: tup = rects[key] x1 = int(tup[0]+ adj_x1[key]) x2 = int(tup[2]+ adj_x2[key]) y1 = int(tup[1] + adj_y1[key]) y2 = int(tup[3] + adj_y2[key]) cv2.rectangle(new_image, (x1, y1),(x2,y2),(0,255,0),2) num_splits = int(abs(y2-y1)//gap) for i in range(0, num_splits+1): y = int(y1 + i*gap) cv2.line(new_image, (x1, y), (x2, y), color, thickness) if key > 0 and key < len(rects) -1 : #竖直线 x = int((x1 + x2)/2) cv2.line(new_image, (x, y1), (x, y2), color, thickness) # 计算数量 if key == 0 or key == (len(rects) -1): tot_spots += num_splits +1 else: tot_spots += 2*(num_splits +1) # 字典对应好 if key == 0 or key == (len(rects) -1): for i in range(0, num_splits+1): cur_len = len(spot_dict) y = int(y1 + i*gap) spot_dict[(x1, y, x2, y+gap)] = cur_len +1 else: for i in range(0, num_splits+1): cur_len = len(spot_dict) y = int(y1 + i*gap) x = int((x1 + x2)/2) spot_dict[(x1, y, x, y+gap)] = cur_len +1 spot_dict[(x, y, x2, y+gap)] = cur_len +2 print("total parking spaces: ", tot_spots, cur_len) if save: filename = 'with_parking.jpg' cv2.imwrite(filename, new_image) return new_image, spot_dict def assign_spots_map(self,image, spot_dict, make_copy = True, color=[255, 0, 0], thickness=2): if make_copy: new_image = np.copy(image) for spot in spot_dict.keys(): (x1, y1, x2, y2) = spot cv2.rectangle(new_image, (int(x1),int(y1)), (int(x2),int(y2)), color, thickness) return new_image def save_images_for_cnn(self,image, spot_dict, folder_name ='cnn_data'): for spot in spot_dict.keys(): (x1, y1, x2, y2) = spot (x1, y1, x2, y2) = (int(x1), int(y1), int(x2), int(y2)) #裁剪 spot_img = image[y1:y2, x1:x2] spot_img = cv2.resize(spot_img, (0,0), fx=2.0, fy=2.0) spot_id = spot_dict[spot] filename = 'spot' + str(spot_id) +'.jpg' print(spot_img.shape, filename, (x1,x2,y1,y2)) cv2.imwrite(os.path.join(folder_name, filename), spot_img) def make_prediction(self,image,model,class_dictionary): #预处理 img = image/255. #转换成4D tensor image = np.expand_dims(img, axis=0) # 用训练好的模型进行训练 class_predicted = model.predict(image) inID = np.argmax(class_predicted[0]) label = class_dictionary[inID] return label def predict_on_image(self,image, spot_dict , model,class_dictionary,make_copy=True, color = [0, 255, 0], alpha=0.5): if make_copy: new_image = np.copy(image) overlay = np.copy(image) self.cv_show('new_image',new_image) cnt_empty = 0 all_spots = 0 for spot in spot_dict.keys(): all_spots += 1 (x1, y1, x2, y2) = spot (x1, y1, x2, y2) = (int(x1), int(y1), int(x2), int(y2)) spot_img = image[y1:y2, x1:x2] spot_img = cv2.resize(spot_img, (48, 48)) label = self.make_prediction(spot_img,model,class_dictionary) if label == 'empty': cv2.rectangle(overlay, (int(x1),int(y1)), (int(x2),int(y2)), color, -1) cnt_empty += 1 cv2.addWeighted(overlay, alpha, new_image, 1 - alpha, 0, new_image) cv2.putText(new_image, "Available: %d spots" %cnt_empty, (30, 95), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 255, 255), 2) cv2.putText(new_image, "Total: %d spots" %all_spots, (30, 125), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 255, 255), 2) save = False if save: filename = 'with_marking.jpg' cv2.imwrite(filename, new_image) self.cv_show('new_image',new_image) return new_image def predict_on_video(self,video_name,final_spot_dict, model,class_dictionary,ret=True): cap = cv2.VideoCapture(video_name) count = 0 while ret: ret, image = cap.read() count += 1 if count == 5: count = 0 new_image = np.copy(image) overlay = np.copy(image) cnt_empty = 0 all_spots = 0 color = [0, 255, 0] alpha=0.5 for spot in final_spot_dict.keys(): all_spots += 1 (x1, y1, x2, y2) = spot (x1, y1, x2, y2) = (int(x1), int(y1), int(x2), int(y2)) spot_img = image[y1:y2, x1:x2] spot_img = cv2.resize(spot_img, (48,48)) label = self.make_prediction(spot_img,model,class_dictionary) if label == 'empty': cv2.rectangle(overlay, (int(x1),int(y1)), (int(x2),int(y2)), color, -1) cnt_empty += 1 cv2.addWeighted(overlay, alpha, new_image, 1 - alpha, 0, new_image) cv2.putText(new_image, "Available: %d spots" %cnt_empty, (30, 95), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 255, 255), 2) cv2.putText(new_image, "Total: %d spots" %all_spots, (30, 125), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 255, 255), 2) cv2.imshow('frame', new_image) if cv2.waitKey(10) & 0xFF == ord('q'): break cv2.destroyAllWindows() cap.release()
1.Jupyter编辑器代码单元格处为In[*],代表当前单元格失效,代码无法执行。解决:查看右上角Python3状态栏右侧图标,实心圆代表连接忙,线性圆代表正常连接。重启kernel内核/将虚拟环境pyenv安装好。
2.查找(修改)jupyter所在的文件夹:
第一:打开Anaconda Prompt,输入指令jupyter notebook --generate-config
保存生成的文件路径,找到配置文件。
第二:找到.py为后缀的文件,用记事本格式打开。
第三:用ctrl+f查找c.NotebookApp.notebook_dir,在单引号后写入新路径。(需要全英文)
第四:右击jupyter图标-属性-目标,将尾部字符串“%…(省略)”删除-应用-确认。
最后检查:进入jupyter,输入以下代码,可得到所在文件夹。
import os
print(os.path.abspath(‘.’))
3.error: (-215:Assertion failed) size.width>0 && size.height>0 in fun
imshow(‘a’,b)中b的路径不对。可能是此处文件名不对,也可能是上一步imread中的路径不对(必须要用英文斜杠 / ,一个斜杠不行用两个)。
4.知识点:
什么是API?
Application Programming Interface程序之间的接口–程序之间的合约。
5.在Python的matplotlib库中,plt.subplot()函数用于添加子图到当前图形中。函数中的参数231表示一个三位数,每一位数都代表了不同的含义。百位数(2)表示子图的行数,十位数(3)表示子图的列数,个位数(1)表示当前正在创建或激活的子图的索引。因此,plt.subplot(231)意味着在一个2行3列的子图网格中,激活或创建第1个子图。
6.腐蚀操作+膨胀操作:当图片中有细小毛躁,可用腐蚀操作去掉,此时图片会有损毁,再用膨胀操作尽力恢复。
7.not enough values to unpack (expected 3, got 2):
计算轮廓的函数已经有更新,现在只能返回两个结果:
digitCnts,hierarchy = cv2.findContours(group.copy(), cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
8.在边缘检测之前执行滤波操作主要有以下几个原因:
降噪:边缘检测往往会受到图像中噪声的干扰,通过滤波可以降低图像中的噪声,提高边缘检测的准确性。
平滑图像:滤波操作可以使图像变得更为平滑,这样在进行边缘检测时,不会受到图像本身纹理和细节的影响,提高边缘检测的精度。
改进检测性能:滤波操作可以抑制边缘检测中可能出现的伪峰和毛刺现象,提高边缘检测的稳定性和性能。
滤波操作对于边缘检测来说是重要的前置处理步骤,它可以帮助我们更好地提取出图像中的边缘信息,提高边缘检测的精度和稳定性。
9.读取图像的路径中不要有中文
10.cnts=cv2.findContours(edged.copy(),cv2.RETR_LIST,cv2.CHAIN_APPROX_SIMPLE)[0]
该函数下标索引更新为0:[0]
11.vscode打开新窗口:左上角 Files -> open new window 然后在新的窗口里把工程再打开一次。
12.归一化的目的:为使结果更加均衡。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。