第5章 Python的图像处理 Python有很强的图像处理能力,本章分别介绍应用Pillow模块和Open CV处理图像的方法。 5.1 图像像素的存储形式   为了方便介绍数据图像处理知识,首先要了解图像在计算机中的存储形式。   1. 灰度图 把黑白图像中的白色与黑色之间按对数关系分为若干等级,称为灰度。灰度图为单通道,一个像素块对应矩阵中的一个数字,数值为0~255, 其中0表示最暗(黑色),255表示最亮(白色)。灰度图用矩阵表示如图5.1所示(为了方便起见,每列像素值都写一样了)。 图5.1 灰度图用矩阵表示   2. RGB模式彩色图   RGB模式彩色图用三维矩阵表示如图5.2所示。 图5.2 RGB模式彩色图用三维矩阵表示 5.2 Pillow模块处理图像 5.2.1 PIL概述 PIL(Python Imaging Library,Python图像处理库)提供了通用的图像处理功能,以及大量有用的基本图像操作,如图像缩放、裁剪、旋转、颜色转换等。   1. 安装Pillow模块   PIL仅支持到Python 2.7版本,Python 3.x的PIL兼容版本称为Pillow(Python 3.x在使用引用模块语句时,仍称为PIL,即import PIL)。在命令行窗口中使用pip安装Pillow模块,其命令为:       pip install pillow      安装过程如图5.3所示。 图5.3 安装Pillow模块   2. Pillow模块的方法 Pillow模块提供了大量用于图像处理的方法,通过创建的图像对象可以调用这些图像处理方法。Pillow模块图像处理的常用方法如表5.1所示。 表5.1 Pillow模块图像处理的常用方法 方法 说明 Image.open("图像文件名") 打开图像文件,返回图像对象 show() 显示图像 Save("文件名") 保存图像文件 resize(宽高元组) 图像缩放 thumbnail() 创建图像的缩略图 rotate() 旋转图像 transpose(Image.FLIP_LEFT_RIGHT) 图像水平翻转 transpose(Image.FLIP_TOP_BOTTOM) 图像垂直翻转 crop(矩形区域元组) 裁剪图像续表 方法 说明 paste(裁剪图像对象,矩形区域) 粘贴图像 ImageGrab.grab(矩形区域元组) 屏幕截图,若区域为空,则表示全屏幕截图 filter(ImageFilter.EDGE_ENHANCE) 图像增强 filter(ImageFilter.BLUR) 图像模糊 filter(ImageFilter.FIND_EDGES) 图像边缘提取 point(lambda i:i*r) 图像点运算。r>1,图像变亮;r<1,图像变暗 format 查看图像格式的属性值 size 查看图像大小的属性值,格式为(宽度,高度) getpixel(坐标元组) 读取指定坐标点的像素的颜色值,参数为(x,y)坐标元组,返回值为红、绿、蓝三色分量的值 putpixel((元组1), (元组2))? 元组2的值改变目标像素元组1的颜色值 split() 将彩色图像分离为红、绿、蓝三个分量通道。例如:r, g, b = im.split() Image.merge(im.mode, (r,g,b)) 将红、绿、蓝三个分量通道合并成一个彩色图像 enhance(n) 对比度增强为原来的n倍(n为实数)。例如: img = ImageEnhance.Contrast(img) img = im.enhance(1.5) # 对比度增强为原图的1.5倍    5.2.2 PIL的图像处理方法   利用 PIL 中的函数,可以从大多数图像格式的文件中读取数据,然后写入最常见的图像格式文件中。PIL中常用的模块为 Image和ImageTk。Image用于加载图像文件,得到PIL图像对象,而ImageTk模块负责对PIL对象进行各种图像处理。例如,要读取一幅图像,可以使用:    from PIL import Image img = Image.open("img1.gif")      上述代码的返回值img是一个PIL图像对象。可以对这个PIL图像对象进行各种处理。下面介绍几个典型的图像处理的应用示例。   1. 图像的打开和显示   【例5.1】 打开和显示图像示例。   程序代码如下:    import tkinter from PIL import Image, ImageTk win = tkinter.Tk() win.title('图像显示') win.geometry('300x300') # 定义窗体的大小300像素×300像素 can = tkinter.Canvas(win, # 创建画布组件 bg='white', # 指定画布组件的背景色 width=300, # 指定画布组件的宽度 height=300) # 指定画布组件的高度 image = Image.open("dukou.jpg") # 打开图像文件 img = ImageTk.PhotoImage(image) # 获取图像像素 can.create_image(160,120,image=img) # 将图像添加到画布组件中 can.pack() # 将画布组件添加到主窗口 win.mainloop()      程序运行结果如图5.4所示。 图5.4 打开和显示图像   2. 建立图像的缩略图   使用 PIL 可以很方便地创建图像的缩略图。PIL 图像对象的thumbnail(size)方法将图像转换为由元组参数设定大小的缩略图。   【例5.2】 建立图像缩略图示例。 程序代码如下:    import tkinter from tkinter import Label from PIL import Image, ImageTk import os win = tkinter.Tk() win.title('建立图像缩略图') win.geometry('200x200') # 定义窗体大小为400像素×200像素 def imgshow(): size = (64, 64) # 设置缩略图尺寸的元组参数 img = Image.open("dukou.jpg") img.thumbnail(size) img.save("dukou(1).jpg", "JPEG") # 保存缩略图为dukou(1).jpg photo = ImageTk.PhotoImage(file='dukou(1).jpg') label = Label(win, image=photo) label.pack() label.image = photo tkinter.Button(win, text='建立图像缩略图 ',command=imgshow).pack() win.mainloop()      运行程序,单击“建立图像缩略图”按钮后,则将当前文件夹中名为dukou.jpg的图像文件生成64×64像素的缩略图,如图5.5所示。 图5.5 生成图像缩略图   3. 增强图像处理   使用 PIL可以很方便地对图像进行各种数字图像处理。例如,应用filter()方法的ImageFilter.EDGE_ENHANCE属性可以将图像的对比度增强。   【例5.3】 增加图像的对比度示例。 程序代码如下:    import tkinter from tkinter import Label from PIL import Image, ImageTk, ImageEnhance, ImageFilter win = tkinter.Tk() win.title('增强图像') win.geometry('400x200') # 定义窗体大小为400像素×200像素 photo = Image.open('dukou.jpg') img1 = ImageTk.PhotoImage(photo) # 获取图像像素 label_1 = Label(win, image=img1) # 显示原图 def imgshow(): img = photo.filter(ImageFilter.EDGE_ENHANCE) img2 = ImageTk.PhotoImage(img) # 获取图像像素 label_2 = Label(win, image=img2).grid(row=1, column=1) # 显示增强后的图 label_2.image = img2 button = tkinter.Button(win, text='增强图像处理 ',command=imgshow) button.grid(row=0, column=0, columnspan=2) label_1.grid(row=1, column=0) win.mainloop()      程序运行结果如图5.6所示。 图5.6 图像增强 5.3 Open CV数字图像处理   Open CV 是Open Source Computer Vision Library 的简称,是一个计算机视觉库。Open CV可用于开发实时的图像处理、计算机视觉以及模式识别程序,目前使用十分广泛。 5.3.1 Open CV模块的安装和导入   1. 安装Open CV模块   Python的Open CV模块名为opencv-python,在命令行窗口中使用pip安装Open CV模块,其命令为:    pip install opencv-python      安装过程如图5.7所示。 图5.7 用pip安装Open CV模块的过程   2. Open CV模块的导入   导入Open CV模块,其名称为cv2,其导入语句如下:    import cv2    5.3.2 图像的读取、显示和保存 调用Open CV模块的相关函数,就可以很方便地对图像进行操作。下面介绍图像的加载、显示和保存函数及其使用方法。   1. 读取图像函数imread() Open CV 的imread()函数可以读取图像文件,返回图像对象。其基本格式为    cv2.imread(img_path, flag)      该函数的参数含义如下:     ● img_path:图像的路径,即使路径错误也不会报错,但打印返回的图像对象为None。     ● flag:    —cv2.IMREAD_COLOR,读取彩色图像,为默认参数,也可以传入1。    —cv2.IMREAD_GRAYSCALE,按灰度模式读取图像,也可以传入0。    —cv2.IMREAD_UNCHANGED,读取图像,包括其alpha通道,也可以传入?1。   2. 显示图像函数imshow() Open CV 的imshow()函数在自适应图像大小的窗口中显示图像。其基本格式为    cv2.imshow(window_name,img)      该函数的参数含义如下:     ● window_name: 指定窗口的名字。     ● img:显示的图像对象。 该函数可以指定多个窗口名称,显示多个图像。   3. 保持窗体显示的函数 由于imread()函数显示图像的窗口会发生闪退,图像无法显示出来。因此,需要使用waitKey()使窗口保持显示状态。waitKey()的格式为:    cv2.waitKey(millseconds)    其中,参数millseconds为设定时间数(毫秒),在该时间内等待键盘事件;当参数为0时,会无限等待。 通常与waitKey()配合使用的还有销毁窗口函数destroyAllWindows(),其格式为:    cv2.destroyAllWindows(window_name)    其中,参数window_name为需要关闭的窗口。   4. 保存图像函数imwrite() 保存图像函数的格式为:    cv2.imwrite((img_path_name, img)    其中,参数img_path_name为保存的文件名,img为需要保存的图像对象。   【例5.4】 使用Open CV模块打开和显示图像示例。   程序代码如下:    import cv2 img = cv2.imread('test.jpg', 0) # 读取图像,参数0表示灰度 cv2.imshow('title', img) # 显示图像,第1个参数为图像窗体的标题 cv2.imwrite('Grey_img.jpg', img) # 保存灰度图像 cv2.waitKey(0) # 等待图像的关闭 cv2.destroyAllWindows() # 关闭和销毁图像窗口      程序运行结果如图5.8所示。 图5.8 使用Open CV打开和显示图像 5.3.3 绘制基本几何图形   1. 在图像上绘制点和直线   1)绘制点 在OPen CV中,当绘制一个半径很小的圆时,就是一个点了。其函数为:    cv2.circle(img, center, radius, color[, thickness[, lineType[,shift]]])      函数的参数含义如下: ● img:要画的圆所在的矩形或图像。 ● center:圆心坐标,如 (100, 100)。 ● radius:半径,如10。 ● color:圆边框颜色,如 (0, 0, 255)表示红色,为BGR。 ● thickness:取正值时表示圆的边框宽度,取负值时表示画一个填充圆形。 ● lineType:圆边框线型,可为 0,4,8。 ● shift:圆心坐标和半径的小数点位数。   2)绘制直线   绘制直线函数为:    cv2.line(img, pt1, pt2, color[, thickness[, lineType[, shift]]])      函数的参数含义如下: ● img:要画的圆所在的矩形或图像。 ● pt1:直线的起点。 ● pt2:直线的终点。 ● color:线条的颜色,如(0, 0, 255)表示红色,为BGR。 ● thickness:线条的宽度 ● lineType:取值4或8(-- 8:8连通线段,-- 4:4连通线段)。   【例5.5】 在图像上绘制点和直线示例。   程序代码如下:    import cv2 as cv point_size = 5 point_color = (0, 0, 255) # BGR thickness = 4 # 可以为 0,4,8 img = cv.imread('xiamen.jpg',1) # 要画的点的坐标 points_list = [(80, 12), (320, 200)] for point in points_list: cv.circle(img, point, point_size, point_color, thickness) # 直线的起点和终点坐标 ptStart = (80, 14) ptEnd = (318, 200) lineType = 4 cv.line(img, ptStart, ptEnd, point_color, thickness, lineType) cv.imshow('a_window', img) cv.waitKey(0) cv.destroyAllWindows()      程序运行结果如图5.9 所示。 图5.9 在图像上绘制点和直线   2. 在图像上绘制矩形和注释文字   1)绘制矩形 绘制矩形函数为:    cv2.rectangle(img,pt1,pt2,color,thickness)      函数的参数意义如下:     ● img:指定的图片。     ● pt1:矩形左上角点的坐标。     ● pt2:矩形右下角点的坐标。     ● color:线条的颜色 (RGB) 或亮度(灰度图像 )(grayscale image)。 ● thickness:组成矩形的线条的粗细程度。取负值(如 CV_FILLED)时函数绘制填充了色彩的矩形。   2)注释文字   显示文字函数为:    cv2.putText(img, str, origin, font, size, color, thickness)    其中,各参数依次是:图片、添加的文字、左上角坐标、字体、字体大小、颜色和字体粗细。   【例5.6】 在图片上显示矩形框及文字。   程序代码如下:    import cv2 as cv color1 = (0, 0, 255) color2 = (255, 0, 0) x = 140 y = 170 row = 150 col = 180 img = cv.imread('face.jpg', 1) cv.rectangle(img, (x, y), (x+row, y+col), color1, 3) cv.putText(img, 'face', (30, 150), cv.FONT_HERSHEY_COMPLEX, 3, color2, 11) cv.imshow('a_window', img) cv.waitKey(0) cv.destroyAllWindows()      程序运行结果如图5.10 所示。 图5.10 在图像上绘制矩形框和文字 5.4 案 例 精 选 5.4.1 用画布绘制图形   画布Canvas是图形用户界面tkinter的组件,它是一个矩形区域,用于绘制图形或作为容器放置其他组件。   Canvas对象包含了大量的绘图方法,表5.2列出了常用的绘图方法。 表5.2 Canvas对象常用的绘图方法 方法 说明 create_line(x1, y1, x2, y2) 绘制一条从(x1,y1)到(x2,y2)的直线 create_rectangle(x1, y1, x2, y2) 绘制一个左上角为(x1,y1)、右下角为(x2,y2)的矩形 create_polygon(x1, y1, x2, y2,x3, y3, x4, y4,x5, y5, x6, y6) 绘制一个顶点为(x1,y1)、(x2,y2)、(x3,y3)、(x4,y4)、(x5,y5)、(x6,y6)的多边形 create_oval(x1, y1, x2, y2, fill='color') 绘制一个左上角为(x1,y1)、右下角为(x2,y2)的外接矩形所包围的圆,fill为填充颜色 create_arc(x1, y1, x2, y2, start=s0,extent=s) 绘制在左上角为(x1,y1),右下角为(x2,y2)的外接矩形所包围的一段圆弧,圆弧角度为s,从s0开始 create_image(w, h, anchor=NE, image=filename) 在w宽、 h高的矩形区域内显示文件名为filename的图像 move(obj, x, y) 移动组件obj。x为水平方向变化量,y为垂直方向变化量      【例5.7】 绘制笑脸。   程序代码如下:    import tkinter import tkinter.messagebox win = tkinter.Tk() win.title('画布示例') win.geometry('250x250') can = tkinter.Canvas(win, height=250, width=250) # 定义画布 io1 = can.create_oval(35,30,210,210, fill='yellow') # 画一个黄色的圆 io2 = can.create_oval(70,70,180,180, fill='black') io3 = can.create_oval(65,70,185,170, outline='yellow', fill='yellow') io4 = can.create_oval(80,100,110,130, fill='black') io5 = can.create_oval(150,100,180,130, fill='black') can.pack() win.mainloop()      程序运行结果如图5.11所示。   【例5.8】 用方向键移动矩形块。   tkinter的画布Canvas类可以用于设计简单动画,使用move(tags、dx、dy)方法实现移动图片或文字等组件。Canvas的update()方法为刷新界面,重新显示画布。   程序代码如下:    import time from tkinter import * x = 50 y = 50 #(1)定义窗口 win = Tk() win.title("移动小矩形块") #(2)定义画布 canvas = Canvas(win,width=400,height=400) canvas.pack() # 显示画布 #(3)定义矩形块 rect = canvas.create_rectangle(x, y, x+30, y+30, fill='red') print(rect) #(4)定义移动小矩形的函数 def moveRect(event): if event.keysym == 'Up': canvas.move(rect, 0, -3) elif event.keysym == 'Down': canvas.move(rect, 0, 3) elif event.keysym == 'Left': canvas.move(rect, -3, 0) elif event.keysym == 'Right': canvas.move(rect, 3, 0) win.update() # 界面刷新 time.sleep(0.05) # 休眠 #(5)绑定方向键 canvas.bind_all('', moveRect) canvas.bind_all('', moveRect) canvas.bind_all('', moveRect) canvas.bind_all('', moveRect) win.mainloop()      程序运行结果如图5.12所示。 图5.12 用方向键移动小矩形块 5.4.2 识别二维码及条形码   pyzbar是Python的一个开源库,用于扫描、识别二维码和条形码信息。用pip安装pyzbar库,其命令为:    pip install pyzbar      【例5.9】 设有条形码图片bar_code.jpg和二维码图片two_bar_code.jpg,编写一个识别二维码及条形码的程序。   程序代码如下:    from pyzbar import pyzbar import matplotlib.pyplot as plt import cv2 # 条码定位及识别 def decode(image, barcodes): # 循环检测到的条形码 for barcode in barcodes: # 提取条形码的边界框的位置 # 画出图像中条形码的边界框 (x, y, w, h) = barcode.rect cv2.rectangle(image, (x, y), (x + w, y + h), (255, 0, 0), 5) # 条形码数据为字节对象,所以如果想在输出图像上 # 画出来,就需要先将它转换为字符串 barcodeData = barcode.data.decode("utf-8") barcodeType = barcode.type # 绘出图像上条形码的数据和条形码类型 text = "{} ({})".format(barcodeData, barcodeType) cv2.putText(image, text, (x, y - 10), cv2.FONT_HERSHEY_SIMPLEX, .8, (255, 0, 0), 2) # 向终端打印条形码数据和条形码类型 print("[INFO] Found {} barcode: {}".format(barcodeType, barcodeData)) plt.figure(figsize=(10,10)) plt.imshow(image) plt.show() # (1)读取条形码图片 image = cv2.imread('tiaoma.jpg') # 找到图像中的条形码并进行解码 barcodes = pyzbar.decode(image) # 识别条形码 decode(image, barcodes) # (2)读取二维码图片 image = cv2.imread('zsmma.jpg') # 找到图像中的二维码并进行解码 barcodes = pyzbar.decode(image) # 识别二维码 decode(image, barcodes)      程序运行结果:    [INFO] 识别 EAN13 barcode: 6902265501114 [INFO] 识别 QRCODE barcode: http://qr61.cn/odmOqs/qAI1Bft      显示结果如图5.13所示。 图5.13 识别条形码及二维码 5.4.3 无人驾驶汽车车道线检测   车道线检测是无人驾驶汽车的一项重要技术。有效地获取车道线信息,对无人驾驶汽车的决策有至关重要的作用。   车道线检测主要步骤及主要算法说明如下。   1.将视频流转换为一帧帧的图像   在本案例中,为了简化程序,将其省略为加载图像。    src = cv2.imread('gaosu_lane.jpg')      2.对图像进行去噪处理   使用高斯滤波对图像进行去噪处理。    src1 = cv2.GaussianBlur(src, (5,5), 0, 0)      3.把图像转换为灰度图    src2 = cv2.cvtColor(src1,cv2.COLOR_BGR2GRAY) cv2.imshow('huidu',src2)    其效果如图5.14所示。 图5.14 灰度图   4.边缘处理,提取图像的轮廓    src3 = cv2.Canny(src2,lthrehlod,hthrehlod) cv2.imshow('bianyuan',src3)      其效果如图5.15所示。 图5.15 图像的轮廓   5.保留感兴趣的区域并提取轮廓图中的直线   因为在实际应用中摄像头固定在车上,所拍摄的图像中特定的部分包含车道线,它一般都位于图片的中下部,所以只需要对这个区域进行处理即可;然后使用霍夫变换原理,提取轮廓图中的直线。    regin = np.array([[(0,src.shape[0]),(460,325), (520,325),(src.shape[1],src.shape[0])]]) mask = np.zeros_like(src3) mask_color = 255 # src3图像的通道数是1,且是灰度图像,所以颜色值为0~255 cv2.fillPoly(mask,regin,mask_color) src4 = cv2.bitwise_and(src3,mask) cv2.imshow('bianyuan',src4)      其效果如图5.16所示。 图5.16 在感兴趣区域提取轮廓图中的直线   6.优化处理并画出车道线   在第5步中提取到的图像中包含了很多直线,其中有很多直线是不需要的,这就需要去掉冗余的直线。具体的办法如下:首先对直线点集根据斜率的正负分成左右两类直线集;然后再分别对左右直线集进行处理。计算直线集的平均斜率和每条直线的斜率与平均斜率的差值,求出其中差值最大的直线,判断该直线的斜率是否大于阈值,如果大于阈值,就将其去除,然后对剩下的直线继续进行相同的操作,直到满足条件为止。   经过优化处理,得到的直线集依然很多,但是范围已经很小了,进一步采用最小二乘拟合的方式,将这些都用直线拟合,得到最后的左右车道线。    good_leftlines = choose_lines(lefts, 0.1) # 左边优化处理后的点 good_rightlines = choose_lines(rights, 0.1) # 右边优化处理后的点 leftpoints = [(x1,y1) for left in good_leftlines for x1,y1,x2,y2 in left] leftpoints = leftpoints+[(x2,y2) for left in good_leftlines for x1,y1,x2,y2 in left] rightpoints = [(x1,y1) for right in good_rightlines for x1,y1,x2,y2 in right] rightpoints = rightpoints+[(x2,y2) for right in good_rightlines for x1,y1,x2,y2 in right] lefttop = clac_edgepoints(leftpoints,325,src.shape[0]) # 左右车道线的端点 righttop = clac_edgepoints(rightpoints,325,src.shape[0]) src6 = np.zeros_like(src5) cv2.line(src6,lefttop[0],lefttop[1],linecolor,linewidth) cv2.line(src6,righttop[0],righttop[1],linecolor,linewidth) cv2.imshow('onlylane',src6)      其效果如图5.17所示。 图5.17 画出车道线   7.图像合成   将所画的车道线与原图像进行叠加,得到合成的图像。    src7 = cv2.addWeighted(src1,0.8,src6,1,0) cv2.imshow('Finally Image',src7)      其效果如图5.18所示。 图5.18 合成后的图像   8.完整的无人驾驶汽车车道线检测程序代码   【例5.10】 无人驾驶汽车车道线检测程序。   程序代码如下:     import cv2  import numpy as np    # 读取图片  src = cv2.imread('line.jpg')    # 高斯降噪  src1 = cv2.GaussianBlur(src, (5,5), 0, 0)  # cv2.imshow('gaosi', src1)    # 灰度处理  src2 = cv2.cvtColor(src1, cv2.COLOR_BGR2GRAY)  # cv2.imshow('huidu', src2)    # 边缘检测  lthrehlod = 50  hthrehlod =150  src3 = cv2.Canny(src2, lthrehlod, hthrehlod)  # cv2.imshow('bianyuan', src3)    # ROI划定区间,并将非此区间变成黑色  regin = np.array([[(0,src.shape[0]), (460,325),  (520,325),(src.shape[1], src.shape[0])]])  mask = np.zeros_like(src3)  mask_color = 255 # src3图像的通道数是1,且是灰度图像,所以颜色值为0~255  cv2.fillPoly(mask,regin, mask_color)  src4 = cv2.bitwise_and(src3, mask)  # cv2.imshow('bianyuan', src4)    # 利用霍夫变换原理找出图中的像素点组成的直线,然后画出来  rho = 1  theta = np.pi/180  threhold =15  minlength = 40  maxlengthgap = 20  lines = cv2.HoughLinesP(src4, rho, theta, threhold, np.array([]),  minlength, maxlengthgap)  # 画线  linecolor =[0, 255, 255]  linewidth = 4  src5 = cv2.cvtColor(src4, cv2.COLOR_GRAY2BGR) # 转换为三通道的图像  lefts =[]  rights =[]  for line in lines:   for x1,y1,x2,y2 in line:   # cv2.line(src5,(x1,y1),(x2,y2),linecolor,linewidth)   # 分左右车道   k = (y2-y1)/(x2-x1)   if k<0:   lefts.append(line)   else:   rights.append(line)    # 优化处理  def choose_lines(lines,threhold): # 过滤斜率差别较大的点   slope =[(y2-y1)/(x2-x1) for line in lines for x1,x2,y1,y2 in line]   while len(lines) >0:   mean = np.mean(slope) # 平均斜率   diff = [abs(s- mean) for s in slope]   idx = np.argmax(diff)   if diff[idx] > threhold:   slope.pop(idx)   lines.pop(idx)   else:   break     return lines    def clac_edgepoints(points,ymin,ymax): # 寻找直线的端点   x = [p[0] for p in points ]   y = [p[1] for p in points ]     k = np.polyfit(y, x, 1)   func = np.poly1d(k)   xmin = int(func(ymin))   xmax = int(func(ymax))     return [(xmin,ymin),(xmax,ymax)]    good_leftlines = choose_lines(lefts, 0.1) # 处理后的点  good_rightlines = choose_lines(rights, 0.1)    leftpoints = [(x1,y1) for left in good_leftlines  for x1,y1,x2,y2 in left]  leftpoints = leftpoints+[(x2,y2) for left in good_leftlines  for x1,y1,x2,y2 in left]  rightpoints = [(x1,y1) for right in good_rightlines  for x1,y1,x2,y2 in right]  rightpoints = rightpoints+[(x2,y2) for right in good_rightlines  for x1,y1,x2,y2 in right]    lefttop = clac_edgepoints(leftpoints,325,src.shape[0]) # 左车道线的端点  righttop = clac_edgepoints(rightpoints,325,src.shape[0]) # 右车道线的端点    src6 = np.zeros_like(src5)  cv2.line(src6, lefttop[0], lefttop[1], linecolor, linewidth)  cv2.line(src6, righttop[0], righttop[1], linecolor, linewidth)    #cv2.imshow('onlylane',src6)    # 图像叠加  src7 = cv2.addWeighted(src1, 0.8, src6, 1, 0)  cv2.imshow('Finally Image', src7)    cv2.waitKey(16000) # 等待图片的关闭  cv2.destroyAllWindows() # 关闭和销毁图片窗口    习 题 5   1. 绘制一个带阴影的小矩形块。   2. 设计一个图片浏览器,单击“上一张”按钮,则显示前一张图片;单击“下一张”按钮,则显示后一张图片。   3. 编写一个程序,显示图像的轮廓,如图5.19所示。          (a) 原图 (b) 轮廓图 图5.19 显示图像的轮廓