使用python-opencv的一個坑
TypeError: Layout of the output array img is incompatible with cv::Mat (step[ndims-1] != elemsize or step[1] != elemsize*nchannels)
錯誤如上,程式碼如下,stackoverflow我也看到幾篇,說的很隨意,沒有解決我的問題,自己研究了很久,或許有人程式碼不太一樣的也有這個錯誤,但是可能使用了別的opencv函式觸發了這個錯誤,我下面詳細解釋一下
#-*- coding:utf-8 -*-
import cv2
import numpy as np
if __name__=="__main__":
#讀入圖片並將通道數翻轉
img = cv2.imread('./test.png')[:,:,::-1]
#拷貝img至img_copy
img_copy = img.copy()
#輸入要畫的框
box = np.array([0, 12, 13, 18, 2, 20, 3, 40])
#畫框
cv2.polylines(img_copy[:, :, ::-1], box.astype(np.int32).reshape(-1,1,2),
isClosed=True, color= (255,255,0), thickness=2)
上述程式碼很簡單,也有相應的解釋,但是這樣使用polylines函式會報錯 (TypeError: Layout of the output array img is incompatible with cv::Mat (step[ndims-1] != elemsize or step[1] != elemsizenchannels)) ,或許有人和我一樣懷疑是拷貝函式哪出問題的了導致資料通道數發生改變,或者導致資料型別發現變化,測試如下:
#-*- coding:utf-8 -*-
import cv2
import numpy as np
if __name__=="__main__":
img = cv2.imread('./test.png')[:,:,::-1]
img_copy = img.copy()
print("img.dtype == img_copy.dtype:{}".format(img.dtype == img_copy.dtype))
print("np.all(np.equal(img,img_copy)):{}".format(np.all(np.equal(img,img_copy))))
上面程式碼輸出
img.dtype == img_copy.dtype:True
np.all(np.equal(img,img_copy)):True
說明資料完全一樣,資料型別也一樣,那問題出在哪了?
這個問題出在幾個點,解釋之前要明確幾個點:
1.python中切片操作,如[:,:,::-1],是淺拷貝(會建立新的物件,但是資料完全來自於切片前的物件)
2.cv2.polylines()函式的輸入也是輸出
3.在numpy裡面,資料有個flags的屬性檢視
看如下程式碼:
#-*- coding:utf-8 -*-
import cv2
import numpy as np
if __name__=="__main__":
#讀入圖片並將通道數翻轉
img = cv2.imread('./test.png')[:,:,::-1]
#拷貝img至im
img_copy = img.copy()
print("img.flags:\n{}".format(img.flags))
print("img_copy.flags:\n{}".format(img_copy.flags))
上述程式碼輸出:
img.flags:
C_CONTIGUOUS : False
F_CONTIGUOUS : False
OWNDATA : False
WRITEABLE : True
ALIGNED : True
WRITEBACKIFCOPY : False
UPDATEIFCOPY : False
img_copy.flags:
C_CONTIGUOUS : True
F_CONTIGUOUS : False
OWNDATA : True
WRITEABLE : True
ALIGNED : True
WRITEBACKIFCOPY : False
UPDATEIFCOPY : False
關於numpy中flags的說明如下(來自官網):
屬性 | 描述 |
---|---|
C_CONTIGUOUS | 資料是在一個單一的C風格的連續段中 |
F_CONTIGUOUS | 資料是在一個單一的Fortran風格的連續段中 |
OWNDATA | 陣列擁有它所使用的記憶體或從另一個物件中借用它 |
WRITEABLE | 資料區域可以被寫入,將該值設定為 False,則資料為只讀 |
ALIGNED | 資料和所有元素都適當地對齊到硬體上 |
WRITEBACKIFCOPY | 這個陣列是另一陣列的副本,當C-API函式PyArray_ResolveWritebackIfCopy呼叫時,源陣列會由這個陣列中的元素更新 |
UPDATEIFCOPY | 這個陣列是其它陣列的一個副本,當這個陣列被釋放時,原陣列的內容將被更新 |
上面輸出可以看出img在記憶體中是c風格不連續的,且是個淺拷貝,而img_copy是c風格連續的,且是深拷貝(自己擁有記憶體,資料自己管理)。通過這個可以知道,兩個記憶體雖然資料是一樣的,但是儲存方式是不一樣的,當呼叫方式如下時
cv2.polylines(img_copy[:, :, ::-1], box.astype(np.int32).reshape(-1,1,2),
isClosed=True, color=(255,255,0), thickness=2)
#或者是下面這種呼叫方式
#img_copy_t = img_copy[:, :, ::-1]
#cv2.polylines(img_copy_t, box.astype(np.int32).reshape(-1,1,2),
#isClosed=True, color=(255,255,0), thickness=2)
呼叫時都會報錯,因為使用[:,:,::-1]後傳給函式的是個淺拷貝c風格不連續的記憶體資料,但是輸出的記憶體是連續的由image_copy管理的,導致輸入輸出不一致報錯,下面幾種方式都不會導致報錯,因為保證的輸入輸出一致性
1.
#-*- coding:utf-8 -*-
import cv2
import numpy as np
if __name__=="__main__":
#讀入圖片並將通道數翻轉
img = cv2.imread('./test.png')
#拷貝img至img_copy
img_copy = img.copy()
#輸入要畫的框
box = np.array([0, 12, 13, 18, 2, 20, 3, 40])
#畫框
cv2.polylines(img_copy, box.astype(np.int32).reshape(-1,1,2),
isClosed=True, color=(255,255,0), thickness=2)
#-*- coding:utf-8 -*-
import cv2
import numpy as np
if __name__=="__main__":
#讀入圖片並將通道數翻轉
img = cv2.imread('./test.png')[:,:,::-1]
#拷貝img至img_copy
img_copy = img.copy()
#輸入要畫的框
box = np.array([0, 12, 13, 18, 2, 20, 3, 40])
#畫框
cv2.polylines(img[:,:,::-1], box.astype(np.int32).reshape(-1,1,2),
isClosed=True, color=(255,255,0), thickness=2)
#-*- coding:utf-8 -*-
import cv2
import numpy as np
if __name__=="__main__":
#讀入圖片並將通道數翻轉
img = cv2.imread('./test.png')
#拷貝img至img_copy
img_copy = img.copy()
#輸入要畫的框
box = np.array([0, 12, 13, 18, 2, 20, 3, 40])
#畫框
img_copy_t = img_copy[:,:,::-1]
cv2.polylines(img_copy_t[:,:,::-1], box.astype(np.int32).reshape(-1,1,2),
isClosed=True, color=(255,255,0), thickness=2)
猜測opencv輸入也是輸出的函式都會有類似問題,嘗試cv2.lines()也有相同問題
如有不對的地方,歡迎指正