1. 程式人生 > >OpenCV之Python學習筆記

OpenCV之Python學習筆記

感謝http://www.mamicode.com/info-detail-966896.html 這個連結的博主收集如此多的關於opencv-python 影象處理的常用知識。

直都在用Python+OpenCV做一些演算法的原型。本來想留下發布一些文章的,可是整理一下就有點無奈了,都是寫零散不成系統的小片段。現在看 到一本國外的新書《OpenCV Computer Vision with Python》,於是就看一遍,順便把自己掌握的東西整合一下,寫成學習筆記了。更需要的朋友參考。

閱讀須知:

        本文不是純粹的譯文,只是比較貼近原文的筆記;
        請設法購買到出版社出版的書,支援正版。

       從書名就能看出來本書是介紹在Python中使用OpenCV,全書分為5章,兩個附錄:

    • 第一章OpenCV設定,介紹如何在Windows、Mac和Ubuntu上設定Pyhton、OpenCV和相關庫的環境。還討論了OpenCV社群、OpenCV文件以及官方的示例程式碼。
    • 第二章處理檔案、攝像頭和GUI,討論OpenCV的I/O功能,接著使用面向物件的設計編寫一個主應用程式,用於顯示攝像頭實時場景、處理鍵盤輸入、將攝像頭寫入視訊檔案和靜態影象檔案。
    • 第三章影象過濾,介紹使用OpenCV、NumPy和SciPy來編寫影象過濾器。過濾器可用於線性顏色操作、曲線顏色操作、模糊化、銳化和尋找邊緣。本章修改第一章的主程式,將過濾器應用到實時攝像頭場景中。
    • 第四章使用Haar Cascades追蹤人臉,本章將編寫一個層次化的人臉追蹤器,使用OpenCV定點陣圖像中的臉部、眼睛、鼻子和嘴巴。同時還編寫了用於複製和改變影象中某塊區域的大小。同樣,本章也將修改之前的主應用程式,讓其可以用於找到並處理攝像頭場景中的人臉。
    • 第五章檢測前景/背景區域和深度。通過本章將瞭解有關OpenCV(在OpenNI和SensorKinect的支援下)從深度攝像頭中獲得的資料型別的資訊。接著編寫一些函式,使用這些資料對前景區域施加一些限制效果。最後將這些函式整合到主程式中,使得在處理人臉之前先進行細化操作。
    • 附錄A,與Pygame整合。修改主程式,用Pygame替換OpenCV來處理特定的I/O事件。(Pygame提供了更多樣的事件處理函式。)
    • 附錄B,為自定義目標生成Haar Cascades,允許我們檢測一系列的OpenCV工具,來對任何型別的目標或模式構建跟蹤器,而不僅僅是人臉。

本文是OpenCV  2 Computer Vision Application Programming Cookbook讀書筆記的第一篇。在筆記中將以Python語言改寫每章的程式碼。

PythonOpenCV的配置這裡就不介紹了。

注意,現在OpenCV for Python就是通過NumPy進行繫結的。所以在使用時必須掌握一些NumPy的相關知識!

影象就是一個矩陣,在OpenCV for Python中,影象就是NumPy中的陣列!

如果讀取影象首先要匯入OpenCV包,方法為:

  1. import cv2  

讀取並顯示影象

在Python中不需要宣告變數,所以也就不需要C++中的cv::Mat xxxxx了。只需這樣:

  1. img = cv2.imread("D:\cat.jpg")  

OpenCV目前支援讀取bmp、jpg、png、tiff等常用格式。更詳細的請參考OpenCV的參考文件。

接著建立一個視窗

  1. cv2.namedWindow("Image")  

然後在視窗中顯示影象

  1. cv2.imshow("Image", img)  

最後還要添上一句:

  1. cv2.waitKey (0)  

如果不添最後一句,在IDLE中執行視窗直接無響應。在命令列中執行的話,則是一閃而過。

完整的程式為:

  1. import cv2   
  2. img = cv2.imread("D:\\cat.jpg")   
  3. cv2.namedWindow("Image")   
  4. cv2.imshow("Image", img)   
  5. cv2.waitKey (0)  
  6. cv2.destroyAllWindows()  

最後釋放視窗是個好習慣!

建立/複製影象


新的OpenCV的介面中沒有CreateImage介面。即沒有cv2.CreateImage這樣的函式。如果要建立影象,需要使用numpy的函式(現在使用OpenCV-Python繫結,numpy是必裝的)。如下:

  1. emptyImage = np.zeros(img.shape, np.uint8)  

在新的OpenCV-Python繫結中,影象使用NumPy陣列的屬性來表示影象的尺寸和通道資訊。如果輸出img.shape,將得到(500, 375, 3),這裡是以OpenCV自帶的cat.jpg為示例。最後的3表示這是一個RGB影象。

也可以複製原有的影象來獲得一副新影象。

  1. emptyImage2 = img.copy();  

如果不怕麻煩,還可以用cvtColor獲得原影象的副本。

  1. emptyImage3=cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)  
  2. #emptyImage3[...]=0  

後面的emptyImage3[...]=0是將其轉成空白的黑色影象。

儲存影象 

儲存影象很簡單,直接用cv2.imwrite即可。

cv2.imwrite("D:\\cat2.jpg", img)

第一個引數是儲存的路徑及檔名,第二個是影象矩陣。其中,imwrite()有個可選的第三個引數,如下:

cv2.imwrite("D:\\cat2.jpg", img,[int(cv2.IMWRITE_JPEG_QUALITY), 5])

第三個引數針對特定的格式: 對於JPEG,其表示的是影象的質量,用0-100的整數表示,預設為95。 注意,cv2.IMWRITE_JPEG_QUALITY型別為Long,必須轉換成int。下面是以不同質量儲存的兩幅圖:技術分享

對於PNG,第三個引數表示的是壓縮級別。cv2.IMWRITE_PNG_COMPRESSION,從0到9,壓縮級別越高,影象尺寸越小。預設級別為3:

  1. cv2.imwrite("./cat.png", img, [int(cv2.IMWRITE_PNG_COMPRESSION), 0])   
  2. cv2.imwrite("./cat2.png", img, [int(cv2.IMWRITE_PNG_COMPRESSION), 9])  

儲存的影象尺寸如下:技術分享

還有一種支援的影象,一般不常用。

完整的程式碼為:

  1. import cv2  
  2. import numpy as np  
  3. img = cv2.imread("./cat.jpg")  
  4. emptyImage = np.zeros(img.shape, np.uint8)  
  5. emptyImage2 = img.copy()  
  6. emptyImage3=cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)  
  7. #emptyImage3[...]=0  
  8. cv2.imshow("EmptyImage", emptyImage)  
  9. cv2.imshow("Image", img)  
  10. cv2.imshow("EmptyImage2", emptyImage2)  
  11. cv2.imshow("EmptyImage3", emptyImage3)  
  12. cv2.imwrite("./cat2.jpg", img, [int(cv2.IMWRITE_JPEG_QUALITY), 5])  
  13. cv2.imwrite("./cat3.jpg", img, [int(cv2.IMWRITE_JPEG_QUALITY), 100])  
  14. cv2.imwrite("./cat.png", img, [int(cv2.IMWRITE_PNG_COMPRESSION), 0])  
  15. cv2.imwrite("./cat2.png", img, [int(cv2.IMWRITE_PNG_COMPRESSION), 9])  
  16. cv2.waitKey (0)  
  17. cv2.destroyAllWindows()  


參考資料:
《OpenCV References Manuel》
《OpenCV  2 Computer Vision Application Programming Cookbook》
《OpenCV Computer Vision with Python》

訪問畫素

畫素的訪問和訪問numpy中ndarray的方法完全一樣,灰度圖為:

  1. img[j,i] = 255  

其中j,i分別表示影象的行和列。對於BGR影象,為:

  1. img[j,i,0]= 255  
  2. img[j,i,1]= 255  
  3. img[j,i,2]= 255  

第三個數表示通道。

下面通過對影象新增人工的椒鹽現象來進一步說明OpenCV Python中需要注意的一些問題。完整程式碼如下:

  1. import cv2  
  2. import numpy as np  
  3. def salt(img, n):  
  4.     for k in range(n):  
  5.         i = int(np.random.random() * img.shape[1]);  
  6.         j = int(np.random.random() * img.shape[0]);  
  7.         if img.ndim == 2:   
  8.             img[j,i] = 255  
  9.         elif img.ndim == 3:   
  10.             img[j,i,0]= 255  
  11.             img[j,i,1]= 255  
  12.             img[j,i,2]= 255  
  13.     return img  
  14. if __name__ == ‘__main__‘:  
  15.     img = cv2.imread("影象路徑")  
  16.     saltImage = salt(img, 500)  
  17.     cv2.imshow("Salt", saltImage)  
  18.     cv2.waitKey(0)  
  19.     cv2.destroyAllWindows()  

處理後能得到類似下面這樣帶有模擬椒鹽現象的圖片:

技術分享

上面的程式碼需要注意幾點:

1、與C++不同,在Python中灰度圖的img.ndim = 2,而C++中灰度圖影象的通道數img.channel() =1

2、為什麼使用np.random.random()?
這裡使用了numpy的隨機數,Python自身也有一個隨機數生成函式。這裡只是一種習慣,np.random模組中擁有更多的方法,而Python自 帶的random只是一個輕量級的模組。不過需要注意的是np.random.seed()不是執行緒安全的,而Python自帶的 random.seed()是執行緒安全的。如果使用隨機數時需要用到多執行緒,建議使用Python自帶的random()和random.seed(), 或者構建一個本地的np.random.Random類的例項。

分離、合併通道

由於OpenCV Python和NumPy結合的很緊,所以即可以使用OpenCV自帶的split函式,也可以直接操作numpy陣列來分離通道。直接法為:

  1. import cv2  
  2. import numpy as np  
  3. img = cv2.imread("D:/cat.jpg")  
  4. b, g, r = cv2.split(img)  
  5. cv2.imshow("Blue", r)  
  6. cv2.imshow("Red", g)  
  7. cv2.imshow("Green", b)  
  8. cv2.waitKey(0)  
  9. cv2.destroyAllWindows()  

其中split返回RGB三個通道,如果只想返回其中一個通道,可以這樣:

  1. b = cv2.split(img)[0]  
  2. g = cv2.split(img)[1]  
  3. r = cv2.split(img)[2]  

最後的索引指出所需要的通道。

也可以直接操作NumPy陣列來達到這一目的:

  1. import cv2  
  2. import numpy as np  
  3. img = cv2.imread("D:/cat.jpg")  
  4. b = np.zeros((img.shape[0],img.shape[1]), dtype=img.dtype)  
  5. g = np.zeros((img.shape[0],img.shape[1]), dtype=img.dtype)  
  6. r = np.zeros((img.shape[0],img.shape[1]), dtype=img.dtype)  
  7. b[:,:] = img[:,:,0]  
  8. g[:,:] = img[:,:,1]  
  9. r[:,:] = img[:,:,2]  
  10. cv2.imshow("Blue", r)  
  11. cv2.imshow("Red", g)  
  12. cv2.imshow("Green", b)  
  13. cv2.waitKey(0)  
  14. cv2.destroyAllWindows()  

注意先要開闢一個相同大小的圖片出來。這是由於numpy中陣列的複製有些需要注意的地方,具體事例如下:

  1. >>> c= np.zeros(img.shape, dtype=img.dtype)  
  2. >>> c[:,:,:] = img[:,:,:]  
  3. >>> d[:,:,:] = img[:,:,:]  
  4. >>> c is a  
  5. False  
  6. >>> d is a  
  7. False  
  8. >>> c.base is a  
  9. False  
  10. >>> d.base is a #注意這裡!!!  
  11. True  

這裡,d只是a的映象,具體請參考《NumPy簡明教程(二,陣列3)》中的“複製和映象”一節。

通道合併

同樣,通道合併也有兩種方法。第一種是OpenCV自帶的merge函式,如下:

  1. merged = cv2.merge([b,g,r]) #前面分離出來的三個通道  

接著是NumPy的方法:

  1. mergedByNp = np.dstack([b,g,r])   

注意:這裡只是演示,實際使用時請用OpenCV自帶的merge函式!用NumPy組合的結果不能在OpenCV中其他函式使用,因為其組合方式與OpenCV自帶的不一樣,如下:

  1. merged = cv2.merge([b,g,r])  
  2. print "Merge by OpenCV"   
  3. print merged.strides  
  4. mergedByNp = np.dstack([b,g,r])   
  5. print "Merge by NumPy "   
  6. print mergedByNp.strides  

結果為:

  1. Merge by OpenCV  
  2. (1125, 3, 1)  
  3. Merge by NumPy  
  4. (1, 500, 187500)  

NumPy陣列的strides屬性表示的是在每個維數上以位元組計算的步長。這怎麼理解呢,看下面這個簡單點的例子:

  1. >>> a = np.arange(6)  
  2. >>> a  
  3. array([0, 1, 2, 3, 4, 5])  
  4. >>> a.strides  
  5. (4,)  

a陣列中每個元素都是NumPy中的整數型別,佔4個位元組,所以第一維中相鄰元素之間的步長為4(個位元組)。

同樣,2維陣列如下:

  1. >>> b = np.arange(12).reshape(3,4)  
  2. >>> b  
  3. array([[ 0,  1,  2,  3],  
  4.        [ 4,  5,  6,  7],  
  5.        [ 8,  9, 10, 11]])  
  6. >>> b.strides  
  7. (16, 4)  

從裡面開始看,裡面是一個4個元素的一維整數陣列,所以步長應該為4。外面是一個含有3個元素,每個元素的長度是4×4=16。所以步長為16。

下面來看下3維陣列:

  1. >>> c = np.arange(27).reshape(3,3,3)  

其結果為:

  1. array([[[ 0,  1,  2],  
  2.         [ 3,  4,  5],  
  3.         [ 6,  7,  8]],  
  4.        [[ 9, 10, 11],  
  5.         [12, 13, 14],  
  6.         [15, 16, 17]],  
  7.        [[18, 19, 20],  
  8.         [21, 22, 23],  
  9.         [24, 25, 26]]])  

根據前面瞭解的,推斷下這個陣列的步長。從裡面開始算,應該為(3×4×3,3×4,4)。驗證一下:

  1. >>> c.strides  
  2. (36, 12, 4)  

完整的程式碼為:

  1. import cv2  
  2. import numpy as np  
  3. img = cv2.imread("D:/cat.jpg")  
  4. b = np.zeros((img.shape[0],img.shape[1]), dtype=img.dtype)  
  5. g = np.zeros((img.shape[0],img.shape[1]), dtype=img.dtype)  
  6. r = np.zeros((img.shape[0],img.shape[1]), dtype=img.dtype)  
  7. b[:,:] = img[:,:,0]  
  8. g[:,:] = img[:,:,1]  
  9. r[:,:] = img[:,:,2]  
  10. merged = cv2.merge([b,g,r])  
  11. print "Merge by OpenCV"   
  12. print merged.strides  
  13. print merged  
  14. mergedByNp = np.dstack([b,g,r])   
  15. print "Merge by NumPy "   
  16. print mergedByNp.strides  
  17. print mergedByNp  
  18. cv2.imshow("Merged", merged)  
  19. cv2.imshow("MergedByNp", merged)  
  20. cv2.imshow("Blue", b)  
  21. cv2.imshow("Red", r)  
  22. cv2.imshow("Green", g)  
  23. cv2.waitKey(0)  
  24. cv2.destroyAllWindows() 

本篇文章介紹如何用OpenCV Python來計算直方圖,並簡略介紹用NumPy和Matplotlib計算和繪製直方圖

直方圖的背景知識、用途什麼的就直接略過去了。這裡直接介紹方法。

計算並顯示直方圖

與C++中一樣,在Python中呼叫的OpenCV直方圖計算函式為cv2.calcHist。

cv2.calcHist的原型為:

  1. cv2.calcHist(images, channels, mask, histSize, ranges[, hist[, accumulate ]]) #返回hist  

通過一個例子來了解其中的各個引數:

  1. #coding=utf-8  
  2. import cv2  
  3. import numpy as np  
  4. image = cv2.imread("D:/histTest.jpg", 0)  
  5. hist = cv2.calcHist([image],  
  6.     [0], #使用的通道  
  7.     None, #沒有使用mask  
  8.     [256], #HistSize  
  9.     [0.0,255.0]) #直方圖柱的範圍  

其中第一個引數必須用方括號括起來。

第二個引數是用於計算直方圖的通道,這裡使用灰度圖計算直方圖,所以就直接使用第一個通道;

第三個引數是Mask,這裡沒有使用,所以用None。

第四個引數是histSize,表示這個直方圖分成多少份(即多少個直方柱)。第二個例子將繪出直方圖,到時候會清楚一點。

第五個引數是表示直方圖中各個畫素的值,[0.0, 256.0]表示直方圖能表示畫素值從0.0到256的畫素。

最後是兩個可選引數,由於直方圖作為函式結果返回了,所以第六個hist就沒有意義了(待確定)

最後一個accumulate是一個布林值,用來表示直方圖是否疊加。

彩色影象不同通道的直方圖

彩色影象不同通道的直方圖

下面來看下彩色影象的直方圖處理。以最著名的lena.jpg為例,首先讀取並分離各通道:

  1. import cv2      
  2. import numpy as np      
  3. img = cv2.imread("D:/lena.jpg")      
  4. b, g, r = cv2.split(img)   

接著計算每個通道的直方圖,這裡將其封裝成一個函式:

  1. def calcAndDrawHist(image, color):    
  2.     hist= cv2.calcHist([image], [0], None, [256], [0.0,255.0])    
  3.     minVal, maxVal, minLoc, maxLoc = cv2.minMaxLoc(hist)    
  4.     histImg = np.zeros([256,256,3], np.uint8)    
  5.     hpt = int(0.9* 256);    
  6.     for h in range(256):    
  7.         intensity = int(hist[h]*hpt/maxVal)    
  8.         cv2.line(histImg,(h,256), (h,256-intensity), color)    
  9.     return histImg;   

這裡只是之前程式碼的簡單封裝,所以註釋就省掉了。

接著在主函式中使用:

  1. if __name__ == ‘__main__‘:    
  2.     img = cv2.imread("D:/lena.jpg")    
  3.     b, g, r = cv2.split(img)    
  4.     histImgB = calcAndDrawHist(b, [255, 0, 0])    
  5.     histImgG = calcAndDrawHist(g, [0, 255, 0])    
  6.     histImgR = calcAndDrawHist(r, [0, 0, 255])    
  7.     cv2.imshow("histImgB", histImgB)    
  8.     cv2.imshow("histImgG", histImgG)    
  9.     cv2.imshow("histImgR", histImgR)    
  10.     cv2.imshow("Img", img)    
  11.     cv2.waitKey(0)    
  12.     cv2.destroyAllWindows()   

這樣就能得到三個通道的直方圖了,如下:技術分享

更進一步

這樣做有點繁瑣,參考abid rahman的做法,無需分離通道,用折線來描繪直方圖的邊界可在一副圖中同時繪製三個通道的直方圖。方法如下:

    相關推薦

    OpenCVPython學習筆記

    感謝http://www.mamicode.com/info-detail-966896.html 這個連結的博主收集如此多的關於opencv-python 影象處理的常用知識。 直都在用Python+OpenCV做一些演算法的原型。本來想留下發布一些文章的

    Opencv for Python 學習筆記 2.1 攝像頭

    這段時間一直耽擱著,沒多少時間靜下心來學習,這兩節的早就寫好了,一直沒有上傳,這樣可不好,勇敢的騷年啊,努力追逐 PY 的美好世界吧!! 本節主要學習的是通過 .VideoCapture()

    Opencv for Python 學習筆記 1.2 影象儲存

    本節主要學習 Opencv 基本影象處理的影象儲存函式 cv2.imwrite() 程式碼如下: #coding:utf-8 import cv2 import numpy as np i

    python框架 Tornado 學習筆記(一)

    tornado pythontornado 一個簡單的服務器的例子:首先,我們需要安裝 tornado ,安裝比較簡單: pip install tornado 測試安裝是否成功,可以打開python 終端,輸入: import tornado.https

    Python學習筆記-數據報表Excel操作模塊

    工作表 excel 字符串 python 利用Python操作Excel的模塊XlsxWriter,可以操作多個工作表的文字、數字、公式、圖表等。XlsxWriter模塊具有以下功能:100%兼容的Excel XLSX文件,支持Excel 2003、Excel 2007等版本;支持所有Ex

    python學習筆記列表與元組

    長度 bsp 最大 一般來說 設置 概述 檢查 常用 而且 一、概述 python包含6種內建的序列,其中列表和元組是最常用的兩種類型。列表和元組的主要區別在於,列表可以修改,元組則不能修改 使用上,如果要根據要求來添加元素,應當使用列表;而由於要求序列不可修改時,此時

    python 學習筆記 13 -- 經常使用的時間模塊time

    分鐘 英文 超過 最好 還原 %x tracking 運動 文檔 Python 沒有包括相應日期和時間的內置類型。只是提供了3個相應的模塊,能夠採用多種表示管理日期和時間值: * time 模塊由底層C庫提供與時間相關的函數。它包括一些函數

    Python學習筆記基本數據結構方法

    ack 字典 訪問 mos span 函數返回 重復 空格 不存在 通用序列操作: 索引,序列中元素從0開始遞增,這些元素可以通過編號訪問 分片,使用索引只能訪問單個元素,分片操作可以訪問一定範圍內的元素。list[a:b]:a和b是兩個索引作為邊界,包含索引a對應函數,

    Python學習筆記文件和流

    關閉 write finall 存儲路徑 大文件 描述 可選參數 針對 硬盤 打開文件:open(name[,mode[,buffering]]),返回一個文件對象,模式(mode)和緩沖(buffering)是兩個可選參數。 假設有一個名為somefile.txt的文件,

    python學習筆記python-nmap安裝

    python首先最新的鏈接地址和《python絕技》上不同,已經修改。下載後tar,然後運行python setup.py installroot@kali:/# wget http://xael.org/pages/python-nmap-0.6.1.tar.gz--2017-03-22 13:41:38-

    python學習筆記06-列表元組字典

    python一、列表str1 = ‘ahjfhaj1knvr‘print(list(str1))print(type(str1))a = [‘a‘,‘1‘,789]print(a)print(type(a))#顯示# [‘a‘, ‘h‘, ‘j‘, ‘f‘, ‘h‘, ‘a‘, ‘j‘, ‘1‘, ‘k‘,

    python學習筆記(十一)函數

    last 函數返回 traceback keep disco show 全局變量 not 默認參數 牛刀小試:   定義一個無參函數 1 >>> def myFirstFunc(): 2 ... print("Hello python

    python學習筆記(十五)集合

    head erro sdi pytho not in 註意 inter ren mod 集合:對應數學中的集合類型。集合中的元素是唯一,且無序的。 創建集合   方法一:使用{},註意python會自動刪除重復元素 >>> number = {1,2,3

    Python學習筆記selenium 定制啟動 chrome 的選項

    httpproxy int debugger 地址 阻止 mac mozilla 我們 from 學習地址:http://blog.csdn.net/vinson0526/article/details/51850929 使用 selenium 時,我們可能需要對 ch

    python學習筆記split()方法與with

    很好 self 所有 簡單 car 版本 指定 操作 發生 Python split()方法 以下內容摘自:http://www.runoob.com/python/att-string-split.html 描述 Python split()通過指定分隔符對字符串進行切片

    Python學習筆記函數與正則

    地址 tee 大於等於 格式 匿名函數 驗證碼 分組 indent 引用 Python函數 Pycharm 常用快捷鍵,例如復制當前行、刪除當前行、批量註釋、縮進、查找和替換。 常用快捷鍵的查詢和配置:Keymap Ctrl + D:復制當前行 Ctrl + E:刪除當前

    Python 學習筆記random 模塊

    class div .cn 使用 學習 隨機 裏的 logs .com 要使用Random 模塊裏的一些隨機數方法需要先導入random 模塊。 下面是幾種常用的隨機數方法: Python 學習筆記之random 模塊

    Python學習筆記面對象與錯誤處理

    實現 單繼承 父類 成對 數據類型 itl 同時 屬性 子類 反射 __import__()函數用於加載類和函數 __import__(name[, globals[, locals[, fromlist[, level]]]]) 參數說明: n

    python學習筆記(六)集合1

    什麽 mos pty this ash 筆記 sca sel 指定 python學習筆記(六)之集合1python中各種類型與其各種方法,都可以使用下面的方法查到:(1)交互模式下用dir()或者help()(2)google集合特點:英語set,有的可變,有的不可變;元素

    python學習筆記(六)集合2

    lock true 可變 對象 屬於 attribute 聯盟 per rec python學習筆記(七)之集合2不變的集合在”python學習筆記(六)之集合1“中以set()來建立集合,這種方式所創建的集合都是可原地修改的集合,或者說是可變的,也就是說是unhashab