要点:
参考:如何用Python-OpenCV实现图片倾斜调整?
比如一张纸的照片是倾斜的,用OpenCV如何实现自动检测出纸的轮廓并调整倾斜角度,让照片变“正”。
import cv2
import numpy as np
def click(event,x,y,flags,param):
if event==cv2.EVENT_LBUTTONDOWN:
if len(pts)<4:
pts.append([x,y])# 只记录前四次鼠标左击的位置
cv2.circle(img,(x,y),1,(0,0,0))
cv2.imshow('img1',img)
else:
cv2.destroyWindow('img1')# 第五次鼠标左击直接关闭图片窗口
img=cv2.imread('files.jpg')
pts=[]
cv2.namedWindow('img1')
cv2.setMouseCallback('img1',click)
cv2.imshow('img1',img)
cv2.waitKey(0)
pts.sort(reverse=False)
print(pts)
width,height=250,350
pts1=np.float32(pts)
pts2=np.float32([[0,0],[width,0],[0,height],[width,height]])
matrix=cv2.getPerspectiveTransform(pts1,pts2)
img2=cv2.warpPerspective(img,matrix,(width,height))
cv2.imshow('img2',img2)
cv2.waitKey(0)
参考:python opencv实现图像矫正功能_python_脚本之家
import cv2
import numpy as np
img = cv2.imread('/home/pzs/图片/1.jpeg')
result3 = img.copy()
img = cv2.GaussianBlur(img,(3,3),0)
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
edges = cv2.Canny(gray,50,150,apertureSize = 3)
cv2.imwrite("canny.jpg", edges)
src = np.float32([[207, 151], [517, 285], [17, 601], [343, 731]])
dst = np.float32([[0, 0], [337, 0], [0, 488], [337, 488]])
m = cv2.getPerspectiveTransform(src, dst)
result = cv2.warpPerspective(result3, m, (337, 488))
cv2.imshow("result", result)
cv2.waitKey(0)
如何找到这4个顶点:
方法有很多种,如:直线检测,轮廓检测,最小外接矩形等。
使用轮廓检测方式:
import cv2
import imutils
img = cv2.imread('1.jpeg')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
blurred = cv2.GaussianBlur(gray, (5, 5), 0)
dilate = cv2.dilate(blurred, cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3)))
edged = cv2.Canny(dilate, 30, 120, 3) # 边缘检测
cnts = cv2.findContours(edged.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) # 轮廓检测
cnts = cnts[0] if imutils.is_cv2() else cnts[1] # 判断是opencv2还是opencv3
docCnt = None
if len(cnts) > 0:
cnts = sorted(cnts, key=cv2.contourArea, reverse=True) # 根据轮廓面积从大到小排序
for c in cnts:
peri = cv2.arcLength(c, True) # 计算轮廓周长
approx = cv2.approxPolyDP(c, 0.02*peri, True) # 轮廓多边形拟合
# 轮廓为4个点表示找到纸张
if len(approx) == 4:
docCnt = approx
break
for peak in docCnt:
peak = peak[0]
cv2.circle(img, tuple(peak), 10, (255, 0, 0))
cv2.imshow('img', img)
cv2.waitKey(0)
重点讲解这个函数
作用:
对目标图像进行近似多边形拟合,使用一个较少顶点的多边形去拟合一个曲线轮廓,要求拟合曲线与实际轮廓曲线的距离小于某一阀值。
函数原形:
cv2.approxPolyDP(curve, epsilon, closed) -> approxCurve
参数:
curve : 图像轮廓点集,一般由轮廓检测得到
epsilon : 原始曲线与近似曲线的最大距离,参数越小,两直线越接近
closed : 得到的近似曲线是否封闭,一般为True
返回值:
approxCurve :返回的拟合后的多边形顶点集。
之前都是通过仿射变换方式,转正后截取。
但是参考了 Cropping Rotated Rectangles from Image with OpenCV
这篇文章,主要就是运用了透视变换,非常巧妙
# -*- coding: utf-8 -*-
"""
File Name: rotation_v3
Description :
date: 2019/5/10
"""
import cv2
import numpy as np
import matplotlib.pylab as plt
import time
img=cv2.imread('3_75.jpg')
# img=img[14:-15,13:-14]
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret, binary = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
contours, hierarchy = cv2.findContours(binary, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
print("num of contours: {}".format(len(contours)))
rect = cv2.minAreaRect(contours[1]) #获取蓝色矩形的中心点、宽高、角度
'''
retc=((202.82777404785156, 94.020751953125),
(38.13406753540039, 276.02105712890625),
-75.0685806274414)
width = int(rect[1][0])
height = int(rect[1][1])
angle = rect[2]
print(angle)
if width < height: #计算角度,为后续做准备
angle = angle - 90
print(angle)
'''
if rect[-1] < -45 or rect[-1] > 45:
rect = (rect[0], (rect[1][1], rect[1][0]), rect[2] - 90)
angle = rect[2]
width = int(rect[1][0])
height = int(rect[1][1])
# if angle < -45:
# angle += 90.0
# #保证旋转为水平
# width,height = height,width
src_pts = cv2.boxPoints(rect)
# box = cv2.boxPoints(rect)
# box = np.int0(box)
# cv2.drawContours(img_box, [box], 0, (0,255,0), 2)
dst_pts = np.array([[0, height],
[0, 0],
[width, 0],
[width, height]], dtype="float32")
M = cv2.getPerspectiveTransform(src_pts, dst_pts)
warped = cv2.warpPerspective(img, M, (width, height))
if angle<=-90: #对-90度以上图片的竖直结果转正
warped = cv2.transpose(warped)
warped = cv2.flip(warped, 0) # 逆时针转90度,如果想顺时针,则0改为1
# warped=warped.transpose
cv2.imshow('wr1',warped)
cv2.waitKey(0)
仿射变换是二维变换,就是在一个平面上,可以通过三个定点,随意的拉拽到想要的形状,但是不能拿起来,所以他的投影还是他拉拽的本身。
透视变换 是三维变换,就是可以拿起来,随意平行、竖直、翻转一定角度等,就是可以拉拽4个定点,所以它的投影会变成不规则的形状,更高级一点。
参考资料: 根据任意矩形(四点坐标)截取指定区域图像
def CutImgeByBox(output,box):
"""
根据任意内接矩形,四点(顺时针)box[[x1,y2],[x2,y2],[x3,y3],[x4,y4]],从输入图像中,截取图像
"""
if type(box)==type([]):
pts1 = np.float32(box)
if pts1.shape==(4,2):
# 变换后的矩形四个顶点坐标
dy=int(np.sqrt((box[0][0]-box[1][0])**2+(box[0][1]-box[1][1])**2))
dx=int(np.sqrt((box[1][0]-box[2][0])**2+(box[1][1]-box[2][1])**2))
pts2 = np.float32([[1, dy+1],
[1, 1],
[1+dx, 1],
[1+dx, 1+dy]])
M = cv2.getPerspectiveTransform(pts1,pts2)
dst = cv2.warpPerspective(output, M, (output.shape[1],output.shape[0]))
target = dst[int(pts2[1][1]):int(pts2[0][1]),int(pts2[1][0]):int(pts2[2][0]),:]
return True,target
else:
print("box shape is wrong ,must be list as (4,2)")
return False,output
else:
print("box type is wrong,must be list as [[x1,y2],[x2,y2],[x3,y3],[x4,y4]]")
return False,output