• 图像的基本操作


    前言

    本专题开始研究计算机处理图像问题,在进入本专题前,请各位小伙伴先熟悉一下python的基础知识,安装好opencv的对应模块~

    图像的读取

    计算机眼中的图像

    我们在屏幕上看到的彩色图片,是由很多的彩色像素组成的,每一个彩色的像素点又是由三个深度不同的单颜色的叠加。这三个颜色分别为绿、蓝、红,矩阵值的大小代表该像素点对应颜色的深度(分为256个等级):
    在这里插入图片描述

    读取图片

    首先我们将图片和.py文件放在一个路径下,然后用以下代码读取图片:

    import cv2
    img=cv2.imread('show_cat.jpg')
    
    print(img.shape) # 读取搭配的图像数据维度
    print(img.dtype) # 读取到的矩阵存储数据的类型
    print(img.size) # 像素点个数(949*1235*3)
    print(type(img))
    print(img)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    我们看一下结果:
    在这里插入图片描述
    这里需要注意一下,这个三维矩阵是计算机认识可以直接识别的样子,但是我们并不能很直观的理解它。实际上,计算机理解这个数组的过程更符合我们竖着看它时所呈现的样子:
    在这里插入图片描述
    然后我们把图片显示出来:

    import cv2
    img=cv2.imread('show_cat.jpg')
    #图像的显示,也可以创建多个窗口
    cv2.imshow('image',img) # 创建一个名为image的窗口,展示以图像形式img数据
    cv2.waitKey(0) # 参数代表等待时间,单位是毫秒。0表示任意键后运行下一句代码
    cv2.destroyAllWindows() # 销毁显示图片
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    但是很多时候,读取彩色图片是不够的,我们需要得到灰度图片。imread函数给我们提供了一个处理方案:

    import cv2
    img=cv2.imread('show_cat.jpg',cv2.IMREAD_GRAYSCALE)
    cv2.imshow('image',img)
    cv2.waitKey(0)
    cv2.destroyAllWindows()
    
    • 1
    • 2
    • 3
    • 4
    • 5

    就可以得到黑白图像了:
    在这里插入图片描述
    imread的第二参数目前有两种选择,cv2.IMREAD_COLOR代表读取彩色图像,并且是默认选项;cv2.IMREAD_GRAYSCALE代表读取灰度图像。大家可以输出一下此时读取到的img值,观察一下和之前有什么区别。
    按照以上的代码,读取图片之后是会自动小会图片的。那么能不能把生成的图片保存下来呢?这就要提到imwrite函数了。在介绍这个函数之前,我们把这几段代码中都用到的相同操作做一个打包,提升代码的可读性:

    def cv_show(name,img,save='0'):
        cv2.imshow(name, img)
        cv2.waitKey(0)
        if save=='0': # 不保存
            cv2.destroyAllWindows()
        else: # 以制定格式和名称保存到当期按路径
            name+=('.'+save)
            cv2.imwrite(name,img) # 将img保存到当期按路径,name参数指定了文件名称和类型
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    那么加入我们希望把刚才的灰色猫保存起来,就可以这样写代码:

    import cv2
    img=cv2.imread('show_cat.jpg',cv2.IMREAD_GRAYSCALE)
    cv_show("gray_cat",img,'png')
    
    • 1
    • 2
    • 3

    就可以在文件夹中找到我们保存的黑白图像了:
    在这里插入图片描述

    视频读取

    除了上面介绍的图片,视频处理也是图像处理中的内容。我们熟悉的视频其实是一系列图像的连续播放,视频中的每一张图片叫做一帧,展示时间为30ms。因此,对视频的处理就是对每一帧的处理。opencv中也给我们提供了cv2.VideoCapture函数,该函数可以通过接收数字来控制不同的设备,当然也可以读取视频文件。下面我们以一段代码对该函数进行细致讲解:

    import cv2
    vc = cv2.VideoCapture('test.mp4')
    if vc.isOpened(): # 检查是否打开正确
        oepn, frame = vc.read()
        '''
        vc.read()会一帧一帧向后取
        open会接收到一个布尔值,如果当前没有超过最后一帧则为True,
        frame为当前帧的图像矩阵
        大家可以运行以下的注释内容查看结果:
        '''
        # cv2.imshow('aaa',frame)
        # cv2.waitKey(0)
        # cv2.destroyAllWindows()
    else: # 文件不能打开始将open标记为False
        open = False
    while open: # 当文件可以打开时进入循环
        ret, frame = vc.read()
        if frame is None: # 运行过程中没有按下esc退出程序,则当取完最后一帧后自行退出
            break
        if ret == True:
            gray = cv2.cvtColor(frame,  cv2.COLOR_BGR2GRAY)
            cv2.imshow('result', gray)
            if cv2.waitKey(10) & 0xFF == 27:
                break  # 播放完所有帧或中途按下esc键,退出循环
            '''
            cv2.waitKey(10)代表每一帧画面停留10ms
            cv2.waitKey()函数具有返回值,在等待期间如果没有按键,返回-1,否则返回按键的ascII值
            0xFF == 27 的含义在于判断是否按下esc键,如果按下esc键则为True
            &和and含义相同
            '''
    
    vc.release()
    cv2.destroyAllWindows()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    大家可以找一段视频文件运行试一下。下面我们再详细解释一下

    if cv2.waitKey(10) & 0xFF == 27:
    
    • 1

    这句代码。首先,运行到这个语句时,会自动执行cv2.waitKey(10),没有按键的时候返回值都是-1,对应的布尔值是True。0x代表16进制数,ff换算成二进制为8位全1,代表了取按键的低八位二进制数值。我们来看代码:

    while True:
        a=int(input('请输入数字:'))
        if a & 0xFF==27: # 低八位对应的二进制数为00011011(27)时,都可以进入。例如输入283,539,1051等
            print('进入了')
        else:
            print('没进入')
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    在这里插入图片描述
    esc按键对应的低八位ASCII码值刚好是27,这下大家明白为什么按esc可以退出战士了吧?

    截取部分图像数据

    我们使用imread函数读取到的数据与列表相似,可以使用切片法获得一部分内容。因此,我们可以利用这个特性让计算机给我们展示图像的一部分:

    import cv2
    img=cv2.imread('show_cat.jpg')
    cat=img[800:1000,0:600] # 三个颜色矩阵,每个矩阵取800到1000行,0到600列
    cv_show('cat',cat)
    
    • 1
    • 2
    • 3
    • 4

    在这里插入图片描述
    大家可能有个疑问,如果我们把代码写成

    cat=img[800:1000,0:600,0]
    
    • 1

    这样会不会展示成绿色的图片呢?并不会,因为我们取到的是一个二维矩阵的话,计算机会默认显示成灰度图片。那么怎么才能展示绿色图片呢?

    颜色通道的提取

    opencv中给我们提供了一个可以分离三个颜色通道的函数split:

    import cv2
    img=cv2.imread('show_cat.jpg')
    b,g,r=cv2.split(img) # 将三条通道拆开
    print('blue:','\n',b,'\n','green:','\n',g,'\n','red:','\n',r)
    
    • 1
    • 2
    • 3
    • 4

    在这里插入图片描述
    能拆开就能组合,opencv同样给我们一个将三个颜色组合为一个的函数merge:

    import cv2
    img=cv2.imread('show_cat.jpg')
    b,g,r=cv2.split(img) # 将三条通道拆开
    
    img=cv2.merge((b,g,r)) # 将三个矩阵道组合
    print(img)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    输出的矩阵就又成了最初时的样子。那么如果我们将三颜色矩阵拆开之后,再将一个矩阵置零,会有什么效果呢:

    import cv2
    img=cv2.imread('show_cat.jpg')
    b,g,r=cv2.split(img) # 将三条通道拆开
    g[:,:]=0 # 绿色矩阵置零
    img=cv2.merge((b,g,r)) # 将三个矩阵道组合
    cv_show('no_g',img,'jpg')
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    在这里插入图片描述
    当然我们也可以用相同的方法去掉两个通道只保留一种颜色。也可以试试这样操作:

    import cv2
    img=cv2.imread('show_cat.jpg')
    # 只保留R
    cur_img = img.copy() # 拷贝一下原图
    cur_img[:,:,0] = 0 # b通道矩阵全设置为0
    cur_img[:,:,1] = 0 # g通道矩阵全设置为0
    cv_show('R_cat',cur_img,'jpg') # 显示只保留r通道的图片
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    在这里插入图片描述

    边界填充

    提到卷积,大家都应该反映过来,为什么要对图片进行边界填充了。那么opencv要如何进行对填充呢?
    先看代码,随后我们会解释关键参数的含义:

    import cv2
    import matplotlib.pyplot as plt
    
    img=cv2.imread('show_dog.jpg')
    top_size, bottom_size, left_size, right_size = (200, 200, 200, 200)  # 上、下、左、右分别填充的像素大小
    # 这里我们将图片上下左右均填充入200像素
    
    replicate = cv2.copyMakeBorder(img, top_size, bottom_size, left_size, right_size, borderType=cv2.BORDER_REPLICATE)
    # copyMakeBorder()函数为图片进行边缘填充
    reflect = cv2.copyMakeBorder(img, top_size, bottom_size, left_size, right_size, cv2.BORDER_REFLECT)
    reflect101 = cv2.copyMakeBorder(img, top_size, bottom_size, left_size, right_size, cv2.BORDER_REFLECT_101)
    wrap = cv2.copyMakeBorder(img, top_size, bottom_size, left_size, right_size, cv2.BORDER_WRAP)
    constant = cv2.copyMakeBorder(img, top_size, bottom_size, left_size, right_size, cv2.BORDER_CONSTANT, value=0)
    
    # 展示填充效果
    plt.subplot(231), plt.imshow(img), plt.title('ORIGINAL')
    plt.subplot(232), plt.imshow(replicate), plt.title('REPLICATE')
    plt.subplot(233), plt.imshow(reflect), plt.title('REFLECT')
    plt.subplot(234), plt.imshow(reflect101), plt.title('REFLECT_101')
    plt.subplot(235), plt.imshow(wrap), plt.title('WRAP')
    plt.subplot(236), plt.imshow(constant), plt.title('CONSTANT')
    # subplot()用于给显示窗口分区域
    # subplot(234)代表将窗口分为两行三列显示,该图片放在四号(第二行第一列)位置
    # title函数为为显示的图片添加标题
    plt.show()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    下面我们分析一下copyMakeBorder()函数的 borderType参数的含义:

    • BORDER_REPLICATE:复制法,将图像最边缘的像素填充到规定填充的区域;
    • BORDER_REFLECT:反射法,如同放了一面镜子对原来的图像进行部分反射。效果如同:cba abcdefgh hgf;
    • BORDER_REFLECT_101:这也是反射法,只不过将反射轴设置为原图像的最边缘像素,效果如同:dcb abcdefgh gfe;
    • BORDER_WRAP:外包装法,可以理解为循环填充:fgh abcdefgh abc;
    • BORDER_CONSTANT:常量法,以常量值进行填充。
      代码运行结果如图:
      在这里插入图片描述

    数值计算

    某些时候我们需要对像素矩阵进行进行集体的加减值运算。先看代码:

    import cv2
    img_dog=cv2.imread('show_dog.jpg')
    img_dog2= img_dog - 10 # 将dog图片的矩阵每个元素都加10
    print(img_dog[100:103,100:103])
    print(img_dog2[100:103,100:103])
    
    • 1
    • 2
    • 3
    • 4
    • 5

    在这里插入图片描述

    当然,我们也可以对两个维度相同的矩阵进行加法:

    print((img_dog+img_dog2)[100:103,100:103])
    
    • 1

    需要注意的是,如果两个矩阵加起来某个元素超过了255,计算机会对该数值进行除256取余的操作,使结果落到0到255之间:
    在这里插入图片描述
    此外,我们还可以用add函数进行两个像素矩阵相加的操作。add函数的返回值是完成相加的新矩阵,不过这种方式加出来的矩阵如果某个元素大于255,那么计算机将会记录该结果为255:

    print((cv2.add(img_dog,img_dog2)[100:103,100:103]))
    
    • 1

    在这里插入图片描述
    如果我们把相加的结果用图片展示出来,就会发现亮度提高了非常多:
    在这里插入图片描述

    图像融合

    既然我们知道了两个像素矩阵可以相加,那么自然也会向如果是两个图片的像素矩阵相加会有什么效果。下面我们要探讨的就是这个问题:
    首先我们必须确保索要相加的两个图片维度相同,如果不同的我们需要手动调整一下:

    import cv2
    img_cat=cv2.imread('show_cat.jpg')
    img_dog=cv2.imread('show_dog.jpg')
    h,w,c=img_dog.shape # 获取一张图片的维度
    img_cat = cv2.resize(img_cat, (w,h)) # 调整另一张图片大小至适维
                                          # resize函数接收矩阵维度要求先w后h
    res = cv2.addWeighted(img_cat, 0.6, img_dog, 0.4, 0) # 提供融合权重和偏执项
    cv_show('res',res)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    在这里插入图片描述

    cv2.addWeighted(img_cat, 0.6, img_dog, 0.4, 0)
    
    • 1

    这句代码表示合成矩阵中,猫的权重占0.6,狗的权重占0.4,0代表为合成的图片提亮0个单位。设x代表猫的图像矩阵,y代表狗的图像矩阵,则res=αx+βy+b,α=0.6,β=0.4,b=0。

    图像缩放

    resize函数可以调整图片到指定维度,实际上就是将原图片放大或缩小到我们需要的维度。这个函数还有两个比较常用的参数,以对图片进行适当的放缩:

    import cv2
    import matplotlib.pyplot as plt
    img_cat=cv2.imread('show_cat.jpg')
    img_dog=cv2.imread('show_dog.jpg')
    h,w,c=img_dog.shape # 获取一张图片的维度
    img_cat1 = cv2.resize(img_cat, (w,h))
    
    img_cat2 = cv2.resize(img_cat, (w,h),fx=2,fy=1) # 错误操作,不能同时进行两种方式的放缩
    
    img_cat3 = cv2.resize(img_cat, (0,0),fx=1.5,fy=1)
    img_cat4 = cv2.resize(img_cat, (0,0),fx=1,fy=1.5)
    
    plt.subplot(221), plt.imshow(img_cat1), plt.title('img_cat1')
    plt.subplot(222), plt.imshow(img_cat2), plt.title('img_cat2')
    plt.subplot(223), plt.imshow(img_cat3), plt.title('img_cat3')
    plt.subplot(224), plt.imshow(img_cat4), plt.title('img_cat4')
    plt.show()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    我们先看一下运行的结果:
    在这里插入图片描述
    不知道大家有没有发现结果中颜色方面的一点小问题。这是由于opencv读取像素矩阵是按照bgr读取的,而matplotlab里绘图函数是按照rgb复原的。我们可以进行颜色通道拆开—排序—组合的方式,以消除显示上的问题。

  • 相关阅读:
    数据库最基础命令的大集合,四类分别有DDL、DCL、DQL、DML,让我给你解释一下吧
    「计算机网络基础合集」
    3、Semaphore&CountDownLatch&CyclicBarrier详解
    javaScript基础
    GameFrameWork框架(Unity3D)使用笔记(六)游戏主流程ProcedureMain——从数据表加载出所需实体
    开源的网易云音乐API项目都是怎么实现的?
    电商数据|淘宝商品数据接口接入|参数|获取商品订单物流|电商数据分析
    使用jmeter快速生成测试报告
    AtCoder Beginner Contest 275(C,E 补)
    Pikachu靶场全关攻略(超详细!)
  • 原文地址:https://blog.csdn.net/weixin_54929649/article/details/126268703