树莓派上都有OpenCV了,是不是再装个摄像头玩[奸笑]
参考:Accessing the Raspberry Pi Camera with OpenCV and Python
1 摄像头连接及配置
摄像头安装及配置我就不写了,懒的翻译原文……
1.1 安装依赖
安装带array子模块的picamera,用以将图像Numpy的array传给OpenCV处理:
source activate cvpip install "picamera[array]"1.2 摄像头图像预览
需要注意的是,树莓派上的vnc-server默认配置是不能将传输摄像头预览图像传送到vnc-client上的,需要修改配置:在树莓派上打开vnc-server的菜单 -> Options...-> TroubleShooting -> 选择Enable experimental direct capture mode。(参考Sending Raspberry Pi Camera preview to a laptop running VNC Viewer)
2 使用Python+OpenCV访问摄像头输出的图片
通过Python将摄像头输出的图片送入OpenCV进行显示,显示时用到了OpenCV的highGUI:
from picamera.array import PiRGBArrayfrom picamera import PiCameraimport timeimport cv2
# 初始化摄像头camera = PiCamera()# 新建原始摄像头数据的引用rawCapture = PiRGBArray(camera)
# 等一小段时间让摄像头启动time.sleep(0.1)
# 从摄像头获取一张图片camera.capture(rawCapture, format="bgr")image = rawCapture.array
# 在屏幕上显示,按下任意键退出cv2.imshow("Image", image)cv2.waitKey(0)cv2.destroyAllWindows()需要注意的是获取图片时使用的矩阵格式为format="bgr",参见4.2. Capturing to an OpenCV object。
使用PiRGBArray对象的好处就是省去了将原始的RGB矩阵封装成各种格式(如JPEG)的步骤,这样读入OpenCV时也就省去了OpenCV将各种格式解码为RGP矩阵的步骤,尤其是在树莓派这种资源受限的设备上能够节省很多算力。
from picamera.array import PiRGBArrayfrom picamera import PiCameraimport timeimport cv2
# 初始化摄像头并设置分辨率和帧率camera = PiCamera()camera.resolution = (640, 480)camera.framerate = 32# 新建原始摄像头数据的引用,指定分辨率rawCapture = PiRGBArray(camera, size=(640, 480))
# 等一小段时间让摄像头启动time.sleep(0.1)
# 从摄像头中获取视频帧for frame in camera.capture_continuous(rawCapture, format="bgr", use_video_port=True): # 直接从摄像头获取使用Numpy矩阵表达的原始图片数据, # 其中`camera.capture_continuous`返回一个`frame`对象, # `frame`对象的`array`属性中存储的就是该帧Numpy矩阵 image = frame.array
# 在highGUI中显示该帧 cv2.imshow("Frame", image) key = cv2.waitKey(1) & 0xFF
# 注意,在读取下一帧前需要清空当前帧,否则代码报错 rawCapture.truncate(0)
# 在highGUI中按q键退出循环 if key == ord("q"): cv2.destroyAllWindows() break3 试一试图像的透视变换
搞事情之前:我想在Python模块搜索路径中加入我的工作环境~/pyworkspace,以后的事情都在这里搞。
因此,在~/berryconda3/envs/cv/lib/python3.5/site-packages下新建workspace.pth文件,写入内容:
/home/pi/pyworkspace搞一个小事情先,试一下OpenCV的性能,比如4 Point OpenCV getPerspective Transform Example,矩形的四点透视变换。
mkdir cvpi && cd cvpitouch __init__.pymkdir transform && cd transformtouch perspective_transform.py透视变换的代码思路:
- 通过Python代码测量图片中目标矩形区域的长宽;
- 计算输出直角坐标系中的矩形顶点坐标
- 利用
cv2.getPerspectiveTransform()函数计算变换矩阵并应用,输出图片
import numpy as npimport cv2
# 使用透视变换还原矩形
def order_points(pts): """ 调整矩形四个点的排列顺序,用以维护有序的坐标列表。
接受一个包含四个坐标的列表,按照左上、右上、右下、左下的顺序重新排列坐标。
Args: pts: 一个shape为(4, 2)的nparray。 Returns: 顺序调整后的nparray,shape依然是(4, 2)。 """ # 初始化包含四个坐标的坐标列表(cv用坐标系,x轴向右,y轴向下),分别对应矩形的左上、右上、右下、左下坐标 rect = np.zeros((4, 2), dtype = "float32")
# 计算每个坐标的x+y,左上应当具有最小的和,而右下则应当具有最大的和 s = pts.sum(axis = 1) rect[0] = pts[np.argmin(s)] rect[2] = pts[np.argmax(s)]
# 计算每个坐标的y-x,右上应具有最小差,左下应具有最大差 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): """ 用透视变换,将图片中使用`pts`标注的透视图矩形区域,变换为鸟瞰图中的矩形区域。
接受一个图片,以及图片中一个矩形的顶点坐标列表,通过透视变换输出该区域的鸟瞰图。
Args: image: 图片RGB矩阵。 pts: 目标矩形区域的顶点坐标列表。 Returns: 透视变换后的矩形区域鸟瞰图片。 """ # 重新排列矩形顶点坐标,初始化左上、右上、右下、左下坐标 rect = order_points(pts) (tl, tr, br, bl) = rect
# 计算矩形宽度:取 右上与左上顶点间的距离 与 右下与左下顶点间的距离 之中的最大值 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))
# 按照左上(0, 0)、右上(0, H)、右下(W, H)、左下(W, 0)的顺序构造变换后的鸟瞰图坐标列表 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测试变换效果:
cd ../..mkdir test && cd testtouch pptf_test.pyfrom cvpi.transform.perspective_transform import four_point_transformimport numpy as npimport argparseimport cv2
# /home/pi/cvpi/test/pptf_test.py# 测试透视变换:# python pptf_test.py --image images/example_01.png --coords "[(73, 239), (356, 117), (475, 265), (187, 443)]"# python pptf_test.py --image images/example_02.png --coords "[(101, 185), (393, 151), (479, 323), (187, 441)]"# python pptf_test.py --image images/example_03.png --coords "[(63, 242), (291, 110), (361, 252), (78, 386)]"
# 构造命令行工具ap = argparse.ArgumentParser()ap.add_argument("-i", "--image", help = "path to the image file")ap.add_argument("-c", "--coords", help = "comma seperated list of source points")args = vars(ap.parse_args())
# load the image and grab the source coordinates (i.e. the list of# of (x, y) points)# 从文件读取图片RGB矩阵,从命令行中读取目标矩形区域的顶点坐标。# NOTE: 出于安全考虑,不鼓励使用`eval`函数,但在此仅为测试代码功能。image = cv2.imread(args["image"])pts = np.array(eval(args["coords"]), dtype = "float32")
# 测试`four_point_transform`的功能。warped = four_point_transform(image, pts)
# 显示图片cv2.imshow("Original", image)cv2.imshow("Warped", warped)cv2.waitKey(0)cv2.destroyAllWindows()
4 试一试OpenCV文档扫描器
在搞事情之前:
mkdir tool && cd tooltouch __init__.py scan.pyconda install -y scikit-image作者还开源了自己封装的OpenCV常用函数,比如上面的透视变换就包含在内,安装imutils工具包:
pip install --upgrade imutils另外,我发现16G的卡已经用了很多空间了,所以推荐删点空间出来:
sudo apt-get purge wolfram-enginesudo apt-get purge libreoffice*sudo apt-get cleansudo apt-get autoremovefrom cvpi.transform.perspective_transform import four_point_transformfrom skimage.filters import threshold_localimport numpy as npimport argparseimport cv2import imutils
# /home/pi/cvpi/tool/scan.py
# 构造命令行工具ap = argparse.ArgumentParser()ap.add_argument("-i", "--image", required = True, help = "Path to the image to be scanned")args = vars(ap.parse_args())
################################################################################# 1 边缘检测################################################################################# 读取图片,计算图片原始高度与目标高度的缩放比例# 备份图片,然后以500为高度成比例缩放图片image = cv2.imread(args["image"])ratio = image.shape[0] / 500.0orig = image.copy()image = imutils.resize(image, height = 500)
# 将图片处理为灰阶图gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)# 使用高斯模糊去掉较为繁杂的边界,目的是仅保留明显的轮廓,有助于轮廓检查gray = cv2.GaussianBlur(gray, (5, 5), 0)edged = cv2.Canny(gray, 75, 200)
# show the original image and the edge detected image# 显示原图与边缘检测的结果对比print("STEP 1: Edge Detection")cv2.imshow("Image", image)cv2.imshow("Edged", edged)cv2.waitKey(0)cv2.destroyAllWindows()
################################################################################# 2 轮廓查找# 文档扫描的对象通常是一页矩形的文档,按照经验矩形有四个顶点。# 因此我们假设,边界检查结果中最大的且具有四个顶点的轮廓即为文档轮廓。################################################################################# 获得轮廓列表:# 第一个参数:传入要进行轮廓查找的图片,由于函数会直接操作该图片,这里使用边缘检测后的图片备份。# 第二个参数:表示使用仅普通列表组织返回的轮廓,# 若指定为RETR_TREE则还会返回为有层级结构的列表(即表示出轮廓之间的包含关系)。# 第三参数表示简单压缩轮廓点集,如将共线段的点简化为线段两端点,节省内存。cnts = cv2.findContours(edged.copy(), cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)# 由于OpenCV2与3返回轮廓检查返回值结构不一样,因此为了代码通用性需要判断OpenCV版本。cnts = cnts[0] if imutils.is_cv2() else cnts[1]# 按面积将轮廓从大到小排序,保留面积最大的5个轮廓。cnts = sorted(cnts, key = cv2.contourArea, reverse = True)[:5]
for c in cnts: # 测量轮廓周长。 peri = cv2.arcLength(c, True) # 使用Douglas-Peucker曲线近似算法,允许2%的周长误差,返回近似后的顶点列表。 approx = cv2.approxPolyDP(c, 0.02 * peri, True)
# 如果近似结果仅包含4个顶点,则假设找到原图片中目标文件的轮廓 if len(approx) == 4: screenCnt = approx break
# 显示目标文件的轮廓print("STEP 2: Find contours of paper")# 参数分别为:要绘制的原图,轮廓点集列表,需要绘制的轮廓(-1表示画出所有),轮廓颜色,轮廓粗细cv2.drawContours(image, [screenCnt], -1, (0, 255, 0), 2)cv2.imshow("Outline", image)cv2.waitKey(0)cv2.destroyAllWindows()
################################################################################# 3 透视变换# 将获得的文档轮廓进行透视变换,输出文档的鸟瞰图。################################################################################# 将轮廓按比例缩放,并应用透视变换。warped = four_point_transform(orig, screenCnt.reshape(4, 2) * ratio)
# 将变换结果灰阶处理,设置阈值以制造出类似扫描仪的黑白效果。warped = cv2.cvtColor(warped, cv2.COLOR_BGR2GRAY)T = threshold_local(warped, 11, offset = 10, method = "gaussian")warped = (warped > T).astype("uint8") * 255
# 显示原图与处理后效果对比print("STEP 3: Apply perspective transform")cv2.imshow("Original", imutils.resize(orig, height = 650))cv2.imshow("Scanned", imutils.resize(warped, height = 650))cv2.waitKey(0)cv2.destroyAllWindows()边缘检测

轮廓查找

透视变换
