Skip to content

1.初体验

1.1. 案例演示

首先:

pip install pillow

代码:

from PIL import Image


IMG = 'boy.jpg'
WIDTH = 80
HEIGHT = 40
OUTPUT = 'jpg2.txt'

# 我们定义的不重复的字符列表,灰度值大(亮)的用列表开头的符号,灰度值小(暗)的用列表末尾的符号
ascii_char = list("$@B%8&WM#*oahkbdpqwmZO0QLCJUYXzcvunxrjft/\|()1{}[]?-_+~<>i!lI;:,\"^`'.                     ")

# 将256灰度映射到90个字符上
def get_char(r,g,b,alpha):

    if alpha == 0:
        return ' '

    length = len(ascii_char)
    gray = int(0.299 * r + 0.587 * g + 0.114 * b)

    return ascii_char[int((gray/256)*length)]#0表示黑,255表示白

if __name__ == '__main__':

    im = Image.open(IMG)
    im = im.resize((WIDTH,HEIGHT))

    txt = ""

    #将图片看成由像素点组成的二维数组,i代表每一行,j代表每一列
    for i in range(HEIGHT):
        for j in range(WIDTH):
            #getpixel()函数的参数是由每个像素点在图片中的相对位置(w,h)组成的元组
            #返回值是一个代表图片像素值的(r,g,b,alpha)元组
            tup = im.getpixel((j, i))

            alpha = 256

            if len(tup) < 4:
                r, g, b = tup
            else:
                r, g, b, alpha = tup

            txt += get_char(r, g, b, alpha)

        txt += '\n'


    #字符画输出到文件
    if OUTPUT:
        with open(OUTPUT,'w') as f:
            f.write(txt)
    else:
        with open("output.txt",'w') as f:
            f.write(txt)

c
                                 $$$odOOOo$$                                    
                           $d||||||<    U|     YO$                              
                        B||||||||Q]      t      w||J$                           
                     B|||||||||||o    )   ;t  < <||||J                          
                   $||||||||||ro< 0      < uUu< p    0t$                        
                  B|||||||||M      o    u h+  Lb      U0)'0                     
                 d|||||||t     )o)'       bLLLLL ]'         $                   
                &|||||||p                  ;8#u     '<))))' 0                   
               $||||||Q)    ';)))U00p0t;     p              $                   
               k|||||t                       ;)     ;U0)  ]                     
               c|||||o     LLLLLLLZ*&qZh&0)' ')        ;u $                     
               n|||||'    )LLLLLLLLLLLLLLLLLLLLLLLLLLLq   $                     
               Q|||||      LLLLLLLLLLLLLLLLLLLLLLLLLLw                          
               $|||||      tLL*wf-\Y*bLLLLLLLLLLLLLLM    $                      
                r||||u      )-----------\dLLLLLLLLL)    $                       
                $|||||        c-----------1LLLLLLW     $                        
                 B||||d;       p-----------0LLLZ      $                         
                  $|||||<         )c--------*;      '$                          
                   $t||||M           'u00$&&&&&&&0)$                            
                     $||||ro*OLLLLLL)       w`|wYkoh$                           
                      $*LLLhw||||||o        0   ; Y $                           
                        |||||||||||n        Y   j ] )                           
                        O|||||||||||w      p bu|b                               
                        $||||||||od;  <pu'          p$  $a<'u$                  
                         ||JOr||||O  )              )$'        $                
                         O||||||||o  <              o          0                
                         $||||||||r    '          <            0                
                   $LLLL&$|||||||||c      ')0))'  ]p           $                
                   $LLLL*$|||||||||||u           w|)          u                 
                         o||||||||||||||wp)uMJt||||o         U                  
                         |||||||||||||||||||||tOB$$ &       $                   
                         |||||||||||||||Y$                                      
                     <   0||||||||||n$                                          
                          u||||||td$                                            
                              UU$                                               
                     $           $                                              
                      $u                                                        
                         $$U))a$

img

c
                       /il!l!i>>>>>>>>>>>>>><<<><~iM         .cUJJJLUYJj        
                    |Illllllll!ii>><>>>>>>><>>>>>>>>+]$   /JCCJJJJJJJJJCCJJ     
                   ,l!!llllll!!>>>>>>>>>>>><>>>>>>>>>>>?UJJJJJCCCJUJCJCCCJJJJ   
                 &ill!!lllllllll!!!i>>>>><>>><>~b0{1>>_JJJCCCCCCCU  JJJJJJJJJU; 
                :lll!!lllllllllllll!>>>!,\.        >8pCJJCCCJ;      JJJJJCJJJJJ 
               oIllllllllllllllli^r                  UJJCCCCJUzYJY  JJCJJJCCCCC?
              8Illllllllll!'                i#t      UJJJJCJJJJJJz  CCCJJCCCJJJC
              z!llll,Ii                    k         JJJJJJJCCCCJv  CCJJJJJJJJCU
              M!lll?                                 JJJJJJJCCCCJ)  JJJJJJJJJJCX
              q!lll`                                 uJJCCCCCCCCC^  JJJJJJJCCJJ[
              8illl,:   k                             rJJJCCCCCCJ,  UJJJJJJCCCu 
               &Ill;                            @B_    ;CJJCCCCCJL]XUJJJJJJCC   
               ,oll[                                     _CJJCCCJJJJJJCCCJU"    
                w8n                                         ,YUJJCCJJLU}  B     
                 08         vq^                               B "W        8     
                  #                                           8 +8        L     
               >O  m                     '             ]      b;{%        (q    
               B  hWx                                 I       \Yn8        Xk    
               B  B o                          )     [   l    {u*8        *U    
                Wx {WB                                        8 @         B     
                  &0  &                                l     BJ p        WB     
                    $%l8               &             -     Oollv W       %>     
                       L8                `.       :      JQW[llLb#@     Ba      
                         B                             Mu} aMllld)M)   8%       
                          dh                        M   \n O&llllM&@  BB        
                             &%              :MB1      &#  BqlIllllB$@!         
                              B w;ll#  ^b          B%X   &%_!llllZ+ld@          
                            \@'Y &!ll#Z  !%8p%B@U    %Bk?!lllllW-i8I!B@         
                           8n  %  BlllIBo      z8%McllllIIQzpJ|Ic8]lllB$        
                         &8    Q% iW!!!i!ll!<!lIlllllM}&M-L#i_l!lllllll@@       
                       #B       B  B~!lllllll;!!I&:n!!h<II!illlllllllll[%b      
                     uB        $%W %i!lll0I<!;%Z<a0;{#W8B<;xoBBBBb+i!llIZ%      
                   ^$j      j%8  B8x!llll!1vM!l!!ll!%Wl!I$x         Wf%#l%%     
             >@oj.        w$     b8Illllll!l!ll!>8%:c8I;B             i!!!@*    
               &B&l     Z8        %llllllllllll+J!BBi~%@%           O' QhI|${   
                   $l @b          8X!llllllllllllI@B%k:M@            W% [M!&8   
                   $o             n&lllllllllll!!llII\%|BI            d8 #8-B1  
                                   8!lllllllllll;0BplIll*%             W% 8v88  
                                   B&8888%8%M#0<Il!llll!l%%             WX 808  
                                   1Willlllllllllllllllll;8@             &Mm8&

1.1. 图像知识

灰度:灰度使用黑色调来表示物体,即用黑色为基准色,不同饱和度的黑色来显示图像。每个灰度对象都具有从0%(白色)到100%(黑色)的亮度值。需要注意这个百分比是以纯黑为基准的百分比,百分比越高颜色越偏黑,百分比越低颜色越偏白。灰度也可认为是亮度,简单的说就是色彩的深浅程度。

image

灰度值:指黑白图像中点的颜色深浅(浓淡)程度,范围一般从0到255,白色为255,黑色为0,故黑白图片也称灰度图像。

灰度图像:灰度图像是每个像素只有一个采样颜色的图像。这类图像通常显示为从最暗黑色到最亮的白色的灰度。灰度图像与黑白图像不同,在计算机图像领域中黑白图像只有黑白两种颜色(0和255),灰度图像在黑色与白色之间还有许多级的颜色深度(0~255)。一幅完整的彩色图像,是由红色、绿色和蓝色三个通道组成的,红色、绿色和蓝色三个通道的缩览图都是以灰度图显示的,用不同的灰度色阶来表示红色、绿色和蓝色在图像中的比重。

image

1.3.1. 灰度图像

在PIL中,图像模式大致分为九种,分别为:1,L,P,RGB,RGBA,CMYK,YCbCr,I, F。 其中L模式为灰度图像。

image

模式"L"

模式"L"为灰度图像,它的每个像素用8个bit位表示,其中0表示黑,255表示白,其它数字表示不同的灰度。在PIL模式中, 从模式"RGB"转换到模式"L",有一个计算公式,即:L = R 299/1000 + G 587/1000+ B * 114/1000(只取整数部分)。 下面将模式"RGB"的图像转换为模式"L"的图像:

from PIL import Image

empire = Image.open('empire.jpg')
print(empire.mode)                       # RGB
empire_1 = empire.convert('L')
print(empire_1.mode)                     # 1
print(empire.size, empire_1.size)        # (1920, 1080) (1920, 1080)
print(empire.getpixel((0, 0)))           # (9, 5, 2)
print(empire.getpixel((10, 120)))        # (21, 16, 10)
print(empire_1.getpixel((0, 0)))         # 5
print(empire_1.getpixel((10, 120)))      # 16

for i in range(empire_1.size[1]):
    for j in range(empire_1.size[0]):
        empire_1.putpixel((j,i), 0)

empire_1.save("empire_L.jpg")

先是打开一个RGB模式的图片,然后将其转换为模式为"L"的图片,可以看到模式变化后,图片大小前后并未发生改变,然后对比了相同坐标位置的不同模式下的像素值,可以看到,对于RGB模式图像,像素值包含R,G,B三点要素的占比,而对于模式为"L"的图像,像素值可以取到0-255之间的任何值。然后校验一下"RGB"与"L"模式转换的像素转换公式:

image

最后,将转换成功的模式为"L"的图像保存,图片显示如下:

image

将开始的2张图片转为灰度图像 image.png

image.png

其中,png透明图像转灰度图像的代码如下(我们后面再解释):

from PIL import Image


if __name__ == '__main__':

    im = Image.open('cat.png')

    for i in range(im.size[0]):
        for j in range(im.size[1]):
            r,g,b,alpha = im.getpixel((i,j))
            gray = int(0.299 * r + 0.587 * g + 0.114 * b)
            im.putpixel((i,j),(gray,gray,gray,alpha))

    im.save('cat_gray.png')

1.3.2. 图像转字符画核心代码解析

  im = Image.open(IMG)
  im = im.resize((WIDTH,HEIGHT))

打开图片文件 将图片大小调整为WIDTH宽,HEIGHT高,目的是为了转换为字符画后好看。

    txt = ""

    #将图片看成由像素点组成的二维数组,i代表每一行,j代表每一列
    for i in range(HEIGHT):
        for j in range(WIDTH):
            #getpixel()函数的参数是由每个像素点在图片中的相对位置(w,h)组成的元组
            #返回值是一个代表图片像素值的(r,g,b,alpha)元组
            tup = im.getpixel((j, i))
            r, g, b = tup
            txt += get_char(r, g, b)

        txt += '\n'#换行

txt = "",定义一个字符串保存最终转换后的文本。 将图片看成由像素点组成的二维数组,i代表每一行,j代表每一列。 嵌套for循环,取出图片中的每一个像素的r,g,b值(通过im.getpixel((j, i))),调用get_char函数,获得该像素点对应的字符。 最后追加到txt上。

看一下get_char函数的实现

ascii_char = list("$@B%8&WM#*oahkbdpqwmZO0QLCJUYXzcvunxrjft/\|()1{}[]?-_+~<>i!lI;:,\"^`'.                     ")

# 将256灰度映射到90个字符上
def get_char(r,g,b):

    length = len(ascii_char)
    gray = int(0.299 * r + 0.587 * g + 0.114 * b)

    return ascii_char[int((gray/256)*length)]#0表示黑,255表示白

ascii_char是一个字符数组,观察字符数组中的每一个字符,可以发现越靠前面的字符越宽,越靠后面的字符越细,到最后变成空白字符。我们使用这样的字符数组来对应灰度图像的黑色深浅程度。白色或浅色的像素对应空白或窄的字符(数组后面的字符),黑色或深色的像素对应更宽的字符(数组前面的字符)。

上面函数稍微复杂点的地方在于最后一行,gray是灰度值,范围是(0---255),数值越大,代表颜色越深;除以256,代表颜色的深浅程度(0---1)之间的一个数,越接近0,颜色越深。再乘以length,找到符号数组中对应的字符,返回出去。

这里除以256而不是255,是防止数组越界。

1.3.3. 透明图片的处理

img

这张机器猫图片的格式是png,是透明图片。

   #将图片看成由像素点组成的二维数组,i代表每一行,j代表每一列
    for i in range(HEIGHT):
        for j in range(WIDTH):
            #getpixel()函数的参数是由每个像素点在图片中的相对位置(w,h)组成的元组
            #返回值是一个代表图片像素值的(r,g,b,alpha)元组
            tup = im.getpixel((j, i))

            alpha = 256

            if len(tup) < 4:
                r, g, b = tup
            else:
                r, g, b, alpha = tup

            txt += get_char(r, g, b, alpha)

对于png格式的图片,getpixel返回的元组中有4个值,比普通rgb格式的图片多了一个参数alpha,这个参数代表透明度。是一个0-255之间的数字,数字越大代表越不透明。

get_char函数加一个判断,如果是透明像素,则返回空白字符。

# 将256灰度映射到70个字符上
def get_char(r,g,b,alpha):

    if alpha == 0:
        return ' '

    length = len(ascii_char)
    gray = int(0.299 * r + 0.587 * g + 0.114 * b)

    return ascii_char[int((gray/256)*length)]#0表示黑,255表示白

1.3.4. 图片加水印

我们通过一段给图片加水印的功能,来理解一下透明度。

from PIL import Image, ImageDraw, ImageFont
# 添加文字水印
# RGBA的意思是(Red-Green-Blue-Alpha)它是在RGB上扩展包括了“alpha”通道,运行对颜色值设置透明度
im = Image.open("boy.jpg").convert('RGBA')
txt=Image.new('RGBA', im.size, (0,0,0,0))    # 图像的大小 颜色的四个属性 数字越大越表现这个通道属性
d=ImageDraw.Draw(txt)##获得一个绘图的上下文环境,可以抽象为一张画布以及笔墨等等的总合

fnt=ImageFont.truetype("c:/Windows/fonts/Candara.ttf", 50)        # 选水印的字体 选暗色字体,显色的无法出现不知原因
d.text((txt.size[0]-420,txt.size[1]-50), "Hello",font=fnt,fill=(0,0,255,250))
txt.save('txt.png')

out=Image.alpha_composite(im,txt)
# out.show()
out.save("water.png")

image.png


1.3.5. python对象序列化与反序列化模块pickle

python对象的序列化与反序列化

特点

1、只能在python中使用,只支持python的基本数据类型。

2、可以处理复杂的序列化语法。(例如自定义的类的方法,游戏的存档等)

一、内存中操作: dumps方法将对象转成字节(序列化) loads方法将字节还原为对象(反序列化)

python
import pickle
#dumps
li = [11,22,33]
r = pickle.dumps(li)
print(r)


#loads
result = pickle.loads(r)
print(result)

二、文件中操作:

python
#dump:
import pickle

li = [11,22,33]
pickle.dump(li,open('db','wb'))

#load
ret = pickle.load(open('db','rb'))
print(ret)

1.3.6. 视频转字符视频

pip install opencv-python

1.3.7. 总体思路

1.打开视频 2.循环读取视频的每一帧,进行前面类似的处理(将图片转换成字符串),将该字符串保存到一个字符串集合中。 3.最后将该字符串集合,序列化到文件中。

导入依赖,定义常量

import cv2
import pickle
ascii_char =  "$@B%8&WM#*oahkbdpqwmZO0QLCJUYXzcvunxrjft/\|()1{}[]?-_+~<>i!lI;:,\"^`'.                     "

filename = 'basketball.mp4'#要转换的视频文件
show_width, show_heigth = 130,40#转换后的每一帧图形的宽,高
dst_path = 'frames2.dat'#转换后要保存的文件名称

1.打开视频

vc = cv2.VideoCapture(filename)  # 加载一个视频
print('processing ' + filename)
if vc.isOpened():  # 正常打开
    rval, frame = vc.read()
else:
    print('open failed! Abort.')
    exit(1)

2.循环读取视频的每一帧,进行前面类似的处理(将图片转换成字符串),将该字符串保存到一个字符串集合中。 这一步是重点

frame_count = 0  # 帧数
outputList = []  # 初始化输出列表
while rval:
    # 循环读取视频帧
    gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)  # 使用opencv转化成灰度图
    gray = cv2.resize(gray, (show_width, show_heigth))  # resize灰度图
    text = ""
    for pixel_line in gray:
        for pixel in pixel_line:  # 字符串拼接
            text += ascii_char[int(pixel / 256 * len(ascii_char))]
        text += "\n"
    outputList.append(text)
    frame_count = frame_count + 1
    if frame_count % 100 == 0:
        print(str(frame_count) + " frames processed")
    rval, frame = vc.read()

3.最后将该字符串集合,序列化到文件中。

    # 持久化
with open(dst_path, 'wb') as f:
    pickle.dump(outputList, f)

print("compeletd!")

1.3.8. 全部代码

import cv2
import pickle
ascii_char =  "$@B%8&WM#*oahkbdpqwmZO0QLCJUYXzcvunxrjft/\|()1{}[]?-_+~<>i!lI;:,\"^`'.                     "

filename = 'basketball.mp4'#要转换的视频文件
show_width, show_heigth = 130,40#转换后的每一帧图形的宽,高
dst_path = 'frames2.dat'#转换后要保存的文件名称

vc = cv2.VideoCapture(filename)  # 加载一个视频
print('processing ' + filename)
if vc.isOpened():  # 正常打开
    rval, frame = vc.read()
else:
    print('open failed! Abort.')
    exit(1)


frame_count = 0  # 帧数
outputList = []  # 初始化输出列表
while rval:
    # 循环读取视频帧
    gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)  # 使用opencv转化成灰度图
    gray = cv2.resize(gray, (show_width, show_heigth))  # resize灰度图
    text = ""
    for pixel_line in gray:
        for pixel in pixel_line:  # 字符串拼接
            text += ascii_char[int(pixel / 256 * len(ascii_char))]
        text += "\n"
    outputList.append(text)
    frame_count = frame_count + 1
    if frame_count % 100 == 0:
        print(str(frame_count) + " frames processed")
    rval, frame = vc.read()


    # 持久化
with open(dst_path, 'wb') as f:
    pickle.dump(outputList, f)

print("compeletd!")

1.3.9. 字符串视频播放

#!/usr/bin/python
import pickle
import time

with open('./frames2.dat', 'rb') as f:
    frameList = pickle.load(f)
    for frame in frameList:
        print(frame)
        time.sleep(0.05)

1.3.10. 通道的理解

image.png

这幅图的本质是一个4003003的一个矩阵 PI[ 400, 300, 3 ] 列 行 分量 说明这个图像有400列,300行,以及在色彩上有三个分量,分别是:

image.png

image.png

image.png

每个分量单独拿出来都是一个400*300(*1)的矩阵 它们并不是彩色的,而是一幅灰度图像 对于一副8bit的图像来说,矩阵元素的取值范围是从0-255(0 - 2^8-1)

矩阵中的元素对应我们所说的像素(pixel),其值即该像素的灰度值,数值越大,像素的颜色越‘白/浅’;数值越小,像素的颜色越’黑/深‘ .

对于图像每个分量来说,它只是灰度,谈论色彩没有意义,它是“黑白”的!(用黑白来描述灰度图像并不准确,用深浅可能更准确一些。)

在图像显示时,我们把图像的R分量放进红色通道里,B分量放进蓝色通道里,G分量放进绿色通道里。经过一系列处理,显示在屏幕上的就是我们所看到的彩色图像了。

通道类似颜料。 想要什么颜色,对应的通道里的灰度值就大一点就行了。(三原色)

随便在椅子上取一个样点,其灰度值分别是(R:179,G:45,B:9)。所以在显示的时候,红色通道里灰度值大,绿色通道和蓝色通道里的灰度值小,显示出来的就是红色(绿色通道里的灰度值又比蓝色大一些,所以最终显示的结果有点接近橘红色)

如果我们交换一下分量放置的顺序,把G分量放进红色通道里,把R分量放进绿色通道里,B分量放进蓝色通道里,会怎么样呢 ?

变成了这样 :

image.png

代码:

from PIL import Image


if __name__ == '__main__':

    im = Image.open('chat.jpg')

    for i in range(im.size[0]):
        for j in range(im.size[1]):
            r,g,b = im.getpixel((i,j))

            im.putpixel((i,j),(g,r,b))

    im.save('chatbak.jpg')

1.3.11. 分离三个通道,分别上色

1.jpg

from PIL import Image

im1 = Image.open("1.jpg")

##图像处理##

# 转换为RGB图像
im1_sp = im1.convert("RGB")

# 将RGB三个通道分开
r, g, b = im1_sp.split()

#上色图保存
r.save("0rr.jpg")
g.save("0gg.jpg")
b.save("0bb.jpg")

# 将RGB分通道图像上色
imd = Image.new("L", im1.size, 0)
r_color = Image.merge("RGB", (r, imd, imd))
g_color = Image.merge("RGB", (imd, g, imd))
b_color = Image.merge("RGB", (imd, imd, b))

#上色图保存
r_color.save("1rr.jpg")
g_color.save("1gg.jpg")
b_color.save("1bb.jpg")

1rr.jpg

1gg.jpg

1bb.jpg

02-图像的加载、显示和输

图像处理的最基本工作,也是第一步工作:读取、显示和输出图片。

1.图像读取

opencv图像读取的函数是:imread(图像路径,加载模式);输入参数有2个:

  1. “图像路径”是一个字符串;使用绝对路径和相对路径都是可以的,但相对路径必须是程序的工作路径。一般的图像格式都是支持的,如bmp,jpg,png,tiff等。
  2. “读取模式”是一个枚举型的整数,用于指定读取图像的颜色类型。缺省值是1,一般在调用时我们可以不输入这个参数,默认值1表示载入三通道的彩色图像。有如下取值:
    • IMREAD_UNCHANGED:取值:-1。不改变原始图像的读取模式。
    • IMREAD_GRAYSCALE:取值:0。将图像转换成灰度图读取。
    • IMREAD_COLOR:取值:1。为默认缺省值,将图像转换成3通道彩色图像读取。
    • IMREAD_ANYDEPTH:取值:2。读取后是灰度图。
    • IMREAD_ANYCOLOR:取值:4。无损读取原始图像。源图像为彩色图像就读取为3通道彩色图像,源图像为灰度图就读取为灰度图。

为更一步准确掌握这些参数的区别,用3幅图像测试下,这三幅图像分别为:”scooter.png” :带alpha通道的彩色图像; “lenna.bmp”:3通道彩色图像; “moon.bmp”:灰度图像。如下图所示: image.png

image.png

image.png

image.png

import cv2

scooter_path = "scooter.png"  # 带alpha通道的彩色图像
lenna_path = "lenna.bmp"  # 3通道彩色图像
moon_path = "moon.bmp"  # 灰度图像

pic = [scooter_path, lenna_path, moon_path]

for p in pic:
    for i in [-1, 0, 1, 2, 4]: # 加载模式的取值
        img = cv2.imread(p, i)
        print(p, i, img.shape)

输出结果:

scooter.png -1 (512, 512, 4)
scooter.png 0 (512, 512)
scooter.png 1 (512, 512, 3)
scooter.png 2 (512, 512)
scooter.png 4 (512, 512, 3)
lenna.bmp -1 (512, 512, 3)
lenna.bmp 0 (512, 512)
lenna.bmp 1 (512, 512, 3)
lenna.bmp 2 (512, 512)
lenna.bmp 4 (512, 512, 3)
moon.bmp -1 (640, 662)
moon.bmp 0 (640, 662)
moon.bmp 1 (640, 662, 3)
moon.bmp 2 (640, 662)
moon.bmp 4 (640, 662)

我们可以从读入图像后的shape中看出一些端倪。

  • 当取值为-1时,即读取模式为IMREAD_UNCHANGED时,源图像是什么样就是什么样。
  • 当取值为0时,即读取模式为IMREAD_GRAYSCALE时,都读取成灰度图像。
  • 当取值为1时,即读取模式为IMREAD_COLOR时,不管源图像是什么,都转换成3通道图像。
  • 当取值为2时,即读取模式为IMREAD_ANYDEPTH时,都读取成了灰度图。
  • 当取值为4时,即读取模式为IMREAD_ANYCOLOR时,源图像为彩色图像就读取为3通道彩色图像,源图像为灰度图就读取为灰度图。

一般来说,将图像读取成统一的模式对于后续的处理非常重要,一般都使用3通道的彩色图像进行处理,所以默认值是1,即不管源图像是什么,统一转成3通道的图像。对于灰度图,也是3通道,只不过每个通道的值都相等。

如果需要特殊处理,例如只处理灰度图,或需要alpha通道,那么就可以灵活使用其它的读取模式。

2.图像显示

import cv2
lenna_path = "lenna.bmp"
img = cv2.imread(lenna_path)
cv2.imshow('lenna', img)
cv2.waitKey()

opencv中的图像显示函数是imshow(title,img),title是显示图片的窗口标题,img就是要显示的图像。如果不添最后一句cv2.waitKey(),执行时窗口是一闪而过。waitKey()表示无限等待。中间可以输入数值,如5000,cv2.waitKey(5000),表示5000毫秒即5秒后自动关闭窗口。

lenna_path = "lnnea.bmp"
img = cv2.imread(lenna_path)
cv2.imshow('lenna', img)
cv2.waitKey()

输出:

error: (-215) size.width>0 && size.height>0 in function cv::imshow

从报错信息可以推断,是图像的size有问题,即没有得到图像的size。换句话说就是没有读取到源图像。仔细检查发现是文件名弄错了。

这里需要注意,调用imread(),就算图像的路径是错的,或者没有这张图片, 也不会报错,但得到的是None。接着往下使用imshow()显示的话就会报错。 所以下次看到这个size的报错信息,一定是图片路径或图片名称错了。

opencv对多个图片输出在同一个窗口并没有直接的支持手段,但有时候我们会有这个需求,这时使用matplotlib搭配使用比较合适,这个以后再说。

3.图像输出

lenna_path = "Input\\lenna.bmp"
img = cv2.imread(lenna_path)
cv2.imwrite('Output\\lenna.jpg', img)

opencv中的图像输出函数是imwrite(path, img);path是输出图片的路径和名称,格式转换在这里只需要换个后缀名即可。img就是要保存的图像。

需要注意的是,如果输出时,指定的输出目录不存在,例如不存在Output目录,imwrite()不会报错,但也不会自动创建目录然后输出。这样做的结果是什么也没有发生。

03-使用matplotlib显示图像

下面将4张图片放在一个窗口中显示。原始图像如下: image.png

使用matplotlib将4幅图像显示在一个窗口的代码如下:

# coding=utf-8
import cv2
import matplotlib.pyplot as plt

scooter_path = "scooter.png"  # 带alpha通道的彩色图像
lenna_path = "lenna.bmp"  # 3通道彩色图像
baboon_path = "baboon.bmp" # 3通道彩色图像
moon_path = "moon.bmp"  # 灰度图像

pic = [scooter_path, lenna_path, baboon_path, moon_path]
plt.figure(figsize=(8, 6))

for i in range(4):
    img = cv2.imread(pic[i])
    plt.subplot(2, 2, i + 1)
    plt.imshow(img)
    plt.title(pic[i])
plt.show()

显示如下:

image.png

可以看出,图像的颜色显示不正常。

这是因为matplotlib使用的颜色模式是我们现在流行的RGB模式,而opencv使用的是BGR模式,即RGB的倒序模式,与我们通常的RGB是反向的。因此在使用matplotlib显示之前需要做一下图像颜色的转换。 注意下面代码中如下这一行: img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)

import cv2
import matplotlib.pyplot as plt

scooter_path = "scooter.png"  # 带alpha通道的彩色图像
lenna_path = "lenna.bmp"  # 3通道彩色图像
baboon_path = "baboon.bmp"  # 3通道彩色图像
moon_path = "moon.bmp"  # 灰度图像

pic = [scooter_path, lenna_path, baboon_path, moon_path]
plt.figure(figsize=(8, 6))

for i in range(4):
    img = cv2.imread(pic[i])
    img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) # 注意这行。
    plt.subplot(2, 2, i + 1)
    plt.imshow(img)
    plt.title(pic[i])
plt.show()

再次输出,这次正常了: image.png

那么问题来了,为什么opencv要采用BGR顺序的模式呢?这又是因为历史原因,早期的opencv开发者采用了BGR顺序,所以一直遗留下来了。那么早期的opencv开发者为什么要用BGR呢,因为那时的许多照相机生产厂家和软件开发商就是采用的BGR模式。

04-图像矩阵操作

学过线性代数的对矩阵并不陌生。一般来说,图像是一个标准的矩形,有着宽度(width)和高度(height)。而矩阵有着行(row)和列(column),矩阵的操作在数学和计算机中的处理都很常见且成熟,于是很自然的就把图像作为一个矩阵,把对图像的操作转换成对矩阵的操作,实际上所有的图像处理工具都是这么做的。

我们看看下面这张图像:

image.png

用opencv读取:

import cv2
mountain = cv2.imread('mountain.bmp', 0) # 读取为灰度图
print("shape=", mountain.shape)
print("type=", type(mountain))

输出结果:

type= <class 'numpy.ndarray'>
shape= (480, 640)

可以看出,图像在读取后,存在了一个ndarray中,这是numpy中的矩阵类型。

源图像的宽度是640,高度是480,我们一般习惯上依次用高度、宽度的顺序。在矩阵中,高度对应的是多少行(row),宽度对应的是多少列(column),而矩阵的顺序是(row,column),所以输出的shape是(480, 640)。

把图像作为矩阵,可以很方便的进行一些操作,例如取图像中某个区域的值,也就是所谓的crop(裁剪)操作。

print(mountain[9:12, 9:12])

输出结果:

[[244 244 244]
 [244 236 244]
 [244 244 236]]

取大一些的区域,并显示出来。

cv2.imshow("crop", mountain[200:400, 200:600])
print(mountain[200:400, 200:600].shape)
cv2.waitKey()

输出结果: image.png

(200, 400)

对于如下的彩色图像: image.png

fruits = cv2.imread('fruits.bmp')
print("type=", type(fruits))
print("shape=", fruits.shape)
print(fruits[100, 100])

输出结果:

type= <class 'numpy.ndarray'>
shape= (480, 512, 3)
[ 52  96 116]

可以看出,彩色图像同样是一个矩阵,只是矩阵中的每一个点不是一个值,而是包含3个值的数组,这3个值就是RGB值,下面的代码查看RGB3通道的图像(注意,opencv的顺序是BGR)。

import matplotlib.pyplot as plt
fruits = cv2.cvtColor(fruits, cv2.COLOR_BGR2RGB)
fruits_all = [fruits, fruits[:, :, 0], fruits[:, :, 1], fruits[:, :, 2]]
channels = ["RGB", "red", "green", "blue"]
for i in range(4):
    plt.subplot(2, 2, i + 1)
    plt.imshow(fruits_all[i], cmap=plt.cm.gray)
    plt.title(channels[i])
plt.show()
for fruit in fruits_all:
    print(fruit.shape)

image.png

(480, 512, 3)
(480, 512)
(480, 512)
(480, 512)

可以看出,每一个RGB通道都是一个矩阵。这3个RGB通道叠在一起形成了彩色图像。

05-图像叠加操作

我们知道2个矩阵是可以执行加法运算的,那2个图像能不能相加呢?当然是可以的。

我们知道2个矩阵相加必须是同一维度的,即行列相同,图像当然也一样,即宽度和高度相同。

我们看下面2副图像: image.png

image.png

import cv2
boats = cv2.imread("boats.bmp", 0)
goldhill = cv2.imread("goldhill.bmp", 0)
print(boats.shape)
print(goldhill.shape)

输出结果:

(576, 720)
(576, 720)

可以看出,这2副图像的宽度和高度分别是720和576,大小是一致的。2个图像相加就是2个矩阵相加。

unite = boats + goldhill
cv2.imshow("unite", unite)
cv2.waitKey()

输出结果:

image.png

不怎么样嘛!白块,黑块怎么那么多!有的地方变白了,有的地方变黑了。what’s wrong?

我们知道,图像的强度取值区间是[0, 255],opencv的图像类型是uint8类型。而numpy的矩阵加法是一种模(mod)操作,即200+60=260 % 256 = 4,所以该亮的地方反而暗淡了。

opencv自带有图像加法操作运算函数:add(x, y)。add使用的是饱和操作,即200+60=260->255。所以使用opencv自带的add()函数效果会更好。

另外,2副图像相加,我们实际上需要的2副图像相对位置强度的平均值,因此在相加前,需要各除以2,然后再相加(注意要整除)。

unite = cv2.add(boats // 2 , goldhill // 2)
cv2.imshow("unite", unite)
cv2.waitKey()

image.png

我们知道,除以2和乘以0.5是等价的。乘以0.5是一个数乘运算,也就是说,图像自身还可以进行这种运算(注意乘以浮点数后矩阵类型发生了改变,而opencv的图像矩阵必须转换成为uint8类型才能正常显示)。

boats20 = 0.2 * boats
boats20 = boats20.astype("uint8")
cv2.imshow("boats20", boats20)
cv2.waitKey()

输出结果: image.png

可以看到,整体效果暗淡了很多。因为所有的像素点的值都乘以了0.2,强度变成了原来的20%。如果乘的数值小于1,图像整体就变暗,如果乘的数值大于1,整体就变亮。

这给了我们混合2张图像的一种比率调和方法,例如最前面2张图,我们想要看到更多的船(注意为确保2张图像的混合值小于255,系数之和应保持等于1)。

unite = 0.75 * boats + 0.25 * goldhill
unite = unite.astype("uint8")
cv2.imshow("unite", unite)
cv2.waitKey()

image.png

当然,opencv已经为我们准备好了函数addWeighted()。代码如下,效果是一样的。

unite = cv2.addWeighted(boats, 0.75, goldhill, 0.25, 0)
cv2.imshow("unite", unite)
cv2.waitKey()

那么:图像为什么要相加呢?为了混合(blend)2张图像。

实质上,图像相加还有一个用途:降噪。同一场景的不同时间采集图像通过相加平均后去除噪声。

06-图像看做函数

计算机视觉旨在从图像中提取有用的信息,这已经被证实是一个极具挑战性的任务。那么图像是什么?或者说我们把图像看作什么?

有人说图像就是一张图片,一个场景,一个矩形(rectangle),一个矩阵(matrix)。我们先看一个图像实例: image.png

这是一张黑白图像,也就是常说的灰度图。更多的图像是彩色的RGB图像。灰度图处理起来更加简单方便,因此这里使用灰度图像,重在理解。

我们把这幅图像加上坐标刻度,如下图所示:

image.png

放到坐标系中后,我们能把一副图像看作是一个二维函数,定义成f(x, y)或者I(x, y)。任何一对空间坐标(x, y)处f的值看作该坐标点处的强度(intensity)或灰度。我们把每一坐标点处的强度在三维空间中看看。

image.png

下面是二维函数f(x, y)=x2+y2的可视化,与上述图像的二维函数相比,无非是坐标的取值范围不同,函数的表达式不一样。

image.png

把图像看作是一个二维函数,将对后续的图像处理理解和计算带来极大的便利,对图像的处理就是对函数的处理。需要强调的是把图像作为二维函数时,它是一个离散函数,且取值范围有所限定,比如x, y轴的坐标值,函数取值也限定在某个区间之内。另外,图像作为函数,不可能得到上面类似f(x, y)=x2+y2这样的表达式。

对于彩色图像,同样可以看作是一个向量函数。f(x, y) = [r(x, y), g(x, y), b(x, y)]

07-给图像添加噪声

如果你把图像看作信号,那么噪声就是干扰信号。我们在采集图像时可能因为各种各样的干扰而引入图像噪声。前面提到,我们可以把图像看作一个函数,那么带有噪声的图像,就可以看作是原始图像函数与噪声函数相加的和。 f(x, y) = I(x, y) + noise

常见的噪声有椒盐噪声(salt and pepper noise),为什么叫椒盐噪声?因为图像的像素点由于噪声影响随机变成了黑点(dark spot)或白点(white spot)。这里的“椒”不是我们常见的红辣椒或青辣椒,而是外国的“胡椒”(香料的一种)。我们知道,胡椒是黑色的,盐是白色的,所以才取了这么个形象的名字。

下面是原始图片: image.png

我们要给图像添加椒盐噪声,就要把图像的像素点的强度值改为黑点或者白点,黑点的强度值是0,白点的强度值是255。原始图像的强度值区间是[0, 255],那么噪声函数中相应点是255或者是-255,加起来就可以达到是0或255的效果。需要注意,椒盐噪声是随机的改变图像中像素点的值为黑点或白点,并不是对每个像素点都进行操作。

下面是生成10%的盐噪声和椒噪声函数矩阵:

import cv2
import numpy as np
peppers = cv2.imread("peppers.bmp", 0)
row, column = peppers.shape
noise_salt = np.random.randint(0, 256, (row, column))
noise_pepper = np.random.randint(0, 256, (row, column))
rand = 0.1
noise_salt = np.where(noise_salt < rand * 256, 255, 0)
noise_pepper = np.where(noise_pepper < rand * 256, -255, 0)

我们要注意,opencv的图像矩阵类型是uint8,低于0和高于255的值并不截断,而是使用了模操作。即200+60=260 % 256 = 4。所以我们需要先将原始图像矩阵和噪声图像矩阵都转成浮点数类型进行相加操作,然后再转回来。

peppers.astype("float")
noise_salt.astype("float")
noise_pepper.astype("float")
salt = peppers + noise_salt
pepper = peppers + noise_pepper
salt = np.where(salt > 255, 255, salt)
pepper = np.where(pepper < 0, 0, pepper)
cv2.imshow("salt", salt.astype("uint8"))
cv2.imshow("pepper", pepper.astype("uint8"))
cv2.waitKey()

image.png

image.png

还有一种常见的噪声是高斯噪声。椒盐噪声出现在随机的像素点位置,而高斯噪声不同,每个像素点都出现噪声。同样的,在opencv中需要将图像矩阵转换成浮点数再进行加法操作,注意这里用了嵌套的where用于截断小于0和大于255的值。

peppers.astype("float")
Gauss_noise = np.random.normal(0, 50, (row, column))
Gauss = peppers + Gauss_noise
Gauss = np.where(Gauss < 0, 0, np.where(Gauss > 255, 255, Gauss))
cv2.imshow("peppers_Gauss", Gauss.astype("uint8"))
cv2.waitKey()

image.png

08-图像降噪

带有噪声的图像可以看作原始图像函数与噪声函数的和

f(x,y)=I(x,y)+Noise(x,y)

那么我们怎样从带有噪声的图像f(x, y)中去掉Noise得到I(x, y)呢?很自然的能想到,既然能加上噪声函数,那么把噪声函数减去不就行了。

是这样的,当然可以这样想。但是,绝大多数时候我们并不知道噪声函数是怎样的,即使知道噪声函数的表达式,但噪声函数一般都是随机的。例如盐噪声就是随机的改变图像中的像素点,你并不知道到底改变的是哪些像素点!

那怎么办呢?我们知道图像带有如下天然的特性:图像中每个像素点的值与其旁边的像素点的值比较接近。这很显然。因为图像是现实世界的反映。我们现实世界是连续的,除了对象边界外,每个对象反映在图像中,其覆盖的区域亮度都比较近似。

既然图像中的每个像素点的值与其旁边像素的值比较接近,如果一个像素点的值被噪声干扰,那么是否可以用其周边区域的像素平均值来代替这个像素点的值呢。

这就是平均操作:即把每个像素的值改变为其与领域像素组成区域的平均值。

领域是针对像素而言的,也就是像素点的周边区域像素。位于坐标(x, y)处的像素p有4个水平和垂直方向的相邻像素,这4个像素称为p的4领域,下图中标黑色的就是4领域。4领域的像素与p都只有1个单位距离。

image.png

p还有4个对角相邻像素,上图中标绿色的方块就是。4领域与4对角领域合称为p的8领域。当然还可以再向外扩展一圈相邻像素。为什么用矩形定义领域区域呢?而不是圆,椭圆呢?这当然是为了计算机表达和处理的方便。图像就是矩阵嘛,领域区域当然也可以看作矩阵。

如果p在图像的边界上,那么其有些相邻像素将位于图像外部,这个我们以后再探讨。

如果因为是椒盐噪声,像素点的值被噪声改变成0或255了,那么通过领域像素的平均值就能把这个像素点的值近似找回来。

我们假设像素点p的值是56,被椒盐噪声改变成了0。如下图所示: image.png

通过像素点p和周围8领域的平均操作:(56+56+55+57+0+57+57+56+56)/9=50,像素点p的值就又找回来了。

下面的代码用于添加1%的盐噪声。

import numpy as np
import cv2
lena = cv2.imread("lena.bmp", 0)
row, column = lena.shape
noise_salt = np.random.randint(0, 256, (row, column))
rand = 0.01
noise_salt = np.where(noise_salt < rand * 256, 255, 0)
lena.astype("float")
noise_salt.astype("float")
salt = lena + noise_salt
salt = np.where(salt > 255, 255, salt)
salt.astype("uint8")

image.png

下面的代码用于平均操作对图像进行降噪,可以看到白点淡化了许多:

reduce = salt[:]
for x in range(1, row - 1):
    for y in range(1, column - 1):
        average = 0
        for i in [-1, 0, 1]:
            for j in [-1, 0, 1]:
                average += salt[x + i, y + j]
        reduce[x, y] = average // 9
reduce.astype("uint8")

image.png

09-加权平均降噪

前面提到,我们想到了用平均操作来降低噪声。降低噪声的意义是显而易见的,当我们需要图像增强,以提升图像的质量时,必须降低甚至消除噪声。

用平均操作来降低噪声是一种好方法,其基于的原理是:每个像素点的值与其周边像素点的值比较接近。

但是,我们进一步分析,如果一个像素点的值没有被噪声污染,那么用这种操作就改变了像素点的真实值,带来了副作用。同时,在像素点的8领域周边像素中,其水平和垂直方向的4领域像素与中心像素的距离是1,而对角像素与中心像素的距离是根号2,距离中心像素更近的像素的值是不是与中心像素的值更接近呢,重要程度更高呢。同样,中心像素的值本身是不是有更大的可能性接近原始值呢,它本身的重要程度是不是也更高呢。

于是,我们很自然的就想到是不是不用绝对平均,而是用加权平均,重要程度高的赋予更高的权重,重要程度低的赋予较低的权重。例如下图所示: image.png

中心点象素的权重是0.25,水平和垂直方向像素的权重是0.125,对角方向像素的权重是0.0625,当然,这些权重的和必须等于1。

我们用加权平均来看一看效果。下图是带有盐噪声的图像。 image.png

import cv2
import numpy as np
salt = cv2.imread("salt_lena.bmp", 0)
row, column = salt.shape
reduce = salt[:]
for x in range(1, row - 1):
    for y in range(1, column - 1):
        reduce[x, y] = 0.0625 * salt[x - 1, y - 1] + 0.125 * salt[x - 1, y] + 0.0625 * salt[x - 1, y + 1] + \
            0.125 * salt[x, y - 1] + 0.25 * salt[x, y] + 0.125 * salt[x, y + 1] + \
            0.0625 * salt[x + 1, y - 1] + 0.125 * salt[x + 1, y] + 0.0625 * salt[x + 1, y + 1]

cv2.imshow("reduce", reduce.astype("uint8"))
cv2.waitKey()

输出结果: image.png

10-图像滤波器(卷积核)

前面提到,我们可以用平均或加权平均来降低噪声,以增强图像。前面我们是对像素点的周边8领域进行的操作,要是我们想对周边更多领域进行操作呢?如果我们想要调整加权的权重值呢?

那么用前2篇文章中的方法显然是非常烦琐且不灵活的。我们注意到,对8领域进行平均或加权平均操作,实质上是对3×3的一个矩形区域进行操作。

image.png

如果相邻像素再扩大一圈,就是5×5的矩形区域: image.png

我们前面说:图像就是矩阵,那么这个相邻像素构成的区域不也是矩阵吗?同样的,不管是平均操作还是加权平均,这个区域矩阵中的每一个像素点都需要乘以一个系数,那么这个系数是不是同样组成了一个矩阵呢,如果是3×3的区域,那么系数矩阵就是:

image.png

根据线性代数的矩阵数乘法则,我们可以把9放在矩阵外部,变成这个形式:

image.png

同样的,加权操作的系数矩阵是这样的: image.png

那么,对图像的平均操作和加权平均操作进行降噪实质上就是用这个系数矩阵与图像中的任意一点的领域区域矩阵进行矩阵点乘,然后求点乘后矩阵的和。

写出公式就是:

image.png

w是大小为n×n的系数矩阵,f(x,y)是点(x,y)处的值,g(x,y)是进行平均操作或加权平均操作后点(x,y)处的值。

import cv2
import numpy as np

salt = cv2.imread("salt_lena.bmp", 0)
row, column = salt.shape
reduce = salt[:]
coefficient = np.array([[1, 2, 1],
                        [2, 4, 2],
                        [1, 2, 1]])
region_row, region_column = coefficient.shape

for x in range((region_row - 1) // 2, row - (region_row - 1) // 2):
    for y in range((region_column - 1)  // 2, column - (region_column - 1) // 2):
        reduce[x, y] = np.sum(coefficient * salt[x - (region_row - 1) // 2:x + (region_row - 1) // 2 + 1,
                                            y - (region_column - 1) // 2:y + (region_column - 1) // 2 + 1]) / np.sum(
            coefficient)

cv2.imshow("reduce_filter", reduce.astype("uint8"))
cv2.waitKey()

image.png

效果是以前的做法是一样的。

这样的话,就把系数矩阵w抽象了出来,如果需要扩大领域操作的范围,就改变系数矩阵的大小n×n,如果需要调整权重,就改变系数矩阵中的相应位置的值。

这个抽象的系数矩阵w我们可以称它为模板、核、窗口、卷积核等等,都是等价的。而这种操作既然是降低噪声,就是过滤掉噪声,借用频率域的术语,又可以称之为滤波操作,那么这个系数矩阵w就称之为滤波器。由于是在图像的二维空间中进行操作,所以又称为空间滤波器。以后你看到模板卷积核窗口滤波器空间滤波器等等术语,都指的是这个东西。当然,卷积的真实含义与这里的操作有一点点的区别,但不必过于拘泥于术语的精确性。

事实上opencv已经给我们封装好了。我们用一个5×5的空间滤波器,使用opencv的filter2D函数。

salt = cv2.imread("salt_lena.bmp", 0)
filter = 1 / 25 * np.ones((5, 5))
reduce_filter = cv2.filter2D(salt, -1, filter)
cv2.imshow("reduce_filter", reduce_filter)
cv2.waitKey()

输出结果: image.png

这里我们用了3×3和5×5的空间滤波器,大小都是奇数,那么可不可以使用4×4,8×8,甚至是5×4,8×7的尺寸呢,当然是可以的。但是,使用奇数尺寸的滤波器可以简化索引,并更为直观,因为需要操作的像素点是落在滤波器的中心位置上。

11-图像模糊

我们前面提到,使用平均操作或加权平均操作可以降低图像的噪声,并由此引出了空间滤波器的概念。

这种平均操作或加权平均操作的空间滤波器,根据平均操作的特点,可以叫做均值滤波器;其主要应用就是降低噪声,根据应用的特点,也可以叫做平滑滤波器

我们再次感受下这种均值滤波器或平滑滤波器的作用。下面是一张哈勃望远镜拍摄的星空原始图像(来自冈萨雷斯的《数字图像处理),我们将这张图像和使用平滑滤波器后生成的图像进行一下对比。

import cv2
import numpy as np
hubble = cv2.imread("hubble.tif", 0)
filter = 1 / 25 * np.ones((5, 5))
hubble_filter = cv2.filter2D(hubble, -1, filter)
cv2.imshow("hubble", hubble)
cv2.imshow("hubble_filter", hubble_filter)
cv2.waitKey()

image.png

可以清楚的感受到,使用均值滤波器或叫平滑滤波器后的图像变得模糊了,这就是平滑滤波器能够降低噪声所带来的副作用。道理其实很简单,因为平滑后,图像中的物体对象内部和边缘也都变得平滑了。物体对象边缘的平滑,使得物体不再具有非常清晰的边界,而物体对象内部的平滑使得对象失去了层次感。这都造成图像变得模糊起来。

从主观意愿上说,我们希望看到清晰的图像,而不是模糊的图像。所以很多时候我们听说还有一种专门进行模糊图像的操作时,感觉不可思议,这有什么用呢。要知道模糊图像只是处理噪声带来的副作用,并不是我们的目的。图像没有噪声的时候,我们用平滑滤波器去模糊图像干什么呢?

还真有一个重要的应用。我们试着把上面的图像使用更大尺寸的滤波器看看。

filter = 1 / 225 * np.ones((15, 15))
hubble_filter = cv2.filter2D(hubble, -1, filter)
cv2.imshow("hubble", hubble)
cv2.imshow("hubble_filter", hubble_filter)
cv2.waitKey()

image.png

我们可以看到,相对于原始图像,一些较小的物体已经融入背景,看不到了,有些物体即使能看到,亮度也明显降低。这样,我们用图像模糊将图像中较大的较亮的物体保留了下来,而其它的物体则消除了。我们进一步通过阈值处理对模糊后的图像进行操作,将最高亮度的25%作为阈值,低于此阈值的赋为0,高于此阈值的赋为255。

threshhold = 0.25 * 255
hubble_filter_th = np.where(hubble_filter < threshhold, 0, 255)
cv2.imshow("hubble", hubble)
cv2.imshow("hubble_filter", hubble_filter_th.astype("uint8"))
cv2.waitKey()

image.png

像这样利用阈值函数处理并基于物体亮度来消除某些物体的操作时很典型的。当我们只想得到感兴趣的物体时,通过图像模糊,可以将那些尺寸和亮度较小的物体过滤掉,较大的物体则易于检测。除了降低噪声,这就是图像平滑(模糊)的另一个重要应用:目标提取

Released under the MIT License.