像素是图像构成的基本单位,需要清晰的了解其在opencv-python的ndarray中如何存储以及如何处理的。
在opencv-python中,通常使用numpy的ndarray来存储图片数据。cv2.imread()的返回值的数据类型即为: <class 'numpy.ndarray'>
那具体是怎么存储的呢? ndarray的各个维度和图片的长宽,BGR等是如何对应的?下面就谈一谈。
1. 像素的存储方式:
1.1:灰度图:
opencv中最小的数据类型是无符号8位数。灰度图中每个像素使用8位存储空间。取值范围0-255.
import cv2
data = cv2.imread("images/test1.webp", cv2.IMREAD_GRAYSCALE)
print("type is:",type(data))
print("Shape:", data.shape)
cv2.imshow("RGB", data)
cv2.waitKey()
cv2.destroyAllWindows()
type is: <class 'numpy.ndarray'>
Shape: (576, 1024)
获取指定图片数据,图像转变为单通道灰度图片。
可以看到,imread()返回的ndarray的shape是(576, 1024)。即(行数,列数)
这个ndarray的每个元素,就是某个像素的灰度值。
例如:
data[0][100] = 255 或者:
data[0,100] = 255
就是把第0行,第100列那个点的像素值修改为白色。
data[30] =255
则表示把第30行全变为白色。(使用到广播)
2. BGR图片:
BGR格式中,每个像素点则需要3个uint8来表示。
import cv2
#data = cv2.imread("images/test1.webp", cv2.IMREAD_GRAYSCALE)
data = cv2.imread("images/test1.webp", cv2.IMREAD_COLOR)
print("type is:",type(data))
print("Shape:", data.shape)
cv2.imshow("RGB", data)
cv2.waitKey()
cv2.destroyAllWindows()
type is: <class 'numpy.ndarray'>
Shape: (576, 1024, 3)
所以ndarray是3个维度,分别为:(行数,列数,颜色通道)。
因为OpenCV使用BGR顺序,所以颜色顺序是B, G, R.
所以,data[0,1, 2]所代表的是第0行,第一列中的R 通道。
若第0行,30列的像素点为绿色:则其值应该为:[0, 255, 0]
img[0,30] 的值为[0, 255, 0]
img[0,30,0]的值为0 (B通道)
img[0,30,1]的值为255 (G通道)
img[0,30,2]的值为0 (R通道)
2. 像素的访问和修改方法:
2.1:利用numpy.arary的索引方式访问:
import cv2 #data = cv2.imread("images/test1.webp", cv2.IMREAD_GRAYSCALE) data = cv2.imread("images/test1.webp", cv2.IMREAD_COLOR) print("type is:",type(data)) print("Shape:", data.shape) for i in range(data.shape[1]): data[30,i] = 255 data[50] =255
print("data[30,0] is:", data[30,0]) print("data[50,0,0] B is:", data[50,0,0]) print("data[50,0,1] G is:", data[50,0,1]) print("data[50,0,2] R is:", data[50,0,1]) cv2.imshow("RGB", data) cv2.waitKey() cv2.destroyAllWindows()
type is: <class 'numpy.ndarray'>
Shape: (576, 1024, 3)
data[30,0] is: [255 255 255]
data[50,0,0] B is: 255
data[50,0,1] G is: 255
data[50,0,2] R is: 255
说明:
data[30,i] 代表第30行的第i个元素。每个元素为:[BGR].
data[50, 0, 0]表示第50行第0列的B通道
data[50, 0, 1]表示第50行第0列的G通道
data[50, 0, 2]表示第50行第0列的R通道
即把第三十行的所有元素修改为白色:[255, 255, 255]
把第五十行的所有元素修改为白色。
2.2:使用numpy.array的item()和itemset()访问和设置:
可以使用numpy的item()和itemset()函数访问和修改像素值。这两个函数都是经过优化处理的,能够更大幅度的提高处理效率,比使用索引快的多。
2.2.1: 灰度图的访问和修改:
#像素的访问2 data = cv2.imread("images/test1.webp", cv2.IMREAD_GRAYSCALE) print("Type is: {}. sharp is:{}".format(type(data), data.shape)) for i in range(data.shape[1]): data.itemset((50,i), 255) cv2.imshow("Demo", data) print("data[50,0] is:", data.item(50,0)) cv2.waitKey() cv2.destroyAllWindows()
对于灰度图,因为data的shape是二维array. 所以:
data.item(行数,列数) 获取指像素(行列指定)的值。
data.itemset((行数,列数), val) 把val值赋予指定行数和列数的像素。
2.2.2: BGR图像的访问和修改:
#像素的访问3--BGR data = cv2.imread("images/test1.webp", cv2.IMREAD_COLOR) print("data type:{}. shape is:{}".format(type(data), data.shape)) cv2.imshow("Before", data) for i in range(data.shape[1]): #for j in range(3): data.itemset((50, i,0), 0) cv2.imshow("After B=0", data) for i in range(data.shape[1]): #for j in range(3): data.itemset((50, i,1), 0) cv2.imshow("After B,G=0", data) for i in range(data.shape[1]): #for j in range(3): data.itemset((50, i,2), 0) cv2.imshow("After B,G,R=0", data) for i in range(data.shape[1]): for j in range(3): data.itemset((50, i, j), 255) cv2.imshow("After B,G,R=255", data) cv2.waitKey() cv2.destroyAllWindows()
因为BGR图像是三维ndarray. [行,列,颜色通道]。 所以data.item(行,列,通道)
data.itemset((行,列,通道), val).
3. ROI区域数据的访问和修改:
在图像处理过程中,我们可能会需要对一块特定区域(Region of Interest)感兴趣.
要获取这块区域的数据。 opencv-python利用了numpy array的切片来做此动作。
data_des = cv2.imread("images/test1.webp", cv2.IMREAD_GRAYSCALE) data_src = cv2.imread("images/test2.png", cv2.IMREAD_GRAYSCALE) print("src file. data type:{}. data shape:{}".format(type(data_src), data_src.shape)) print("des file. data type:{}. data shape:{}".format(type(data_des), data_des.shape)) cv2.imshow("Src Before", data_src) cv2.imshow("des Before", data_des) rect_roi = data_src[300:400, 600:800] cv2.imshow("Rect ROI", rect_roi) data_des[200:200+rect_roi.shape[0], 400:400+rect_roi.shape[1]] = rect_roi cv2.imshow("des after", data_des)
利用numpy的切片获取其中一部分数据的访问权。
4. BGR图像通道操作:
4.1:通道拆分:
在RGB图像中,一张图像是由R,G,B三个通道构成的。OpenCV中,通道是B,G,R顺序存储的。
若要拆分成三个通道可以有两种方法:
4.1.1:利用numpy array的索引:
b = img[:,:,0]
g = img[:,:,1]
r = img[:,:,2]
#通道拆分: image_src = cv2.imread("images/test1.webp", cv2.IMREAD_COLOR) b = image_src[:, :, 0] g = image_src[:, :, 1] r = image_src[:, :, 2] print("b , g, r shape: {} {} {}".format(b.shape, g.shape, r.shape)) cv2.imshow("Orz", image_src) cv2.imshow("B", b) cv2.imshow("G", g) cv2.imshow("R", r) image_src[:, :, 0] = 0 image_src[:, :, 1] = 0 cv2.imshow("After", image_src) cv2.waitKey() cv2.destroyAllWindows()
可以看到,拆分后,每份数据均为:(行x列)
若把B通道,G通道均值0. 则只剩下R通道的数据,所以变红色。
4.1.2:通过函数拆分:
使用cv2.split()
b,g,r = cv2.split(image)
#通道拆分2: image_src = cv2.imread("images/test2.png", cv2.IMREAD_COLOR) cv2.imshow("Before", image_src) b,g,r = cv2.split(image_src) cv2.imshow("B", b) cv2.imshow("G", g) cv2.imshow("R", r) b1 = cv2.split(image_src)[0] g1 = cv2.split(image_src)[1] r1 = cv2.split(image_src)[2] cv2.waitKey() cv2.destroyAllWindows()
注意:有两个方式。
b,g,r = cv2.split(image_src)
b1 = cv2.split(image_src)[0]
g1 = cv2.split(image_src)[1]
r1 = cv2.split(image_src)[2]
两种方式等价。
4.2:通道合并:
通道合并是通道拆分的逆过程。
使用cv2.merge([b,g,r])
#通道合并 image_src = cv2.imread("images/test1.webp", cv2.IMREAD_COLOR) cv2.imshow("Before", image_src) b,g,r = cv2.split(image_src) bgr = cv2.merge([b,g,r]) cv2.imshow("BGR", bgr) rgb = cv2.merge([r,g,b]) cv2.imshow("RGB", rgb) cv2.waitKey() cv2.destroyAllWindows()
可以看到,把三个通道的数据,合并成BGR顺序的和RGB顺序的。
5. 图片的拷贝:
ndarray如果只是赋值,则其实另一个ndarray和src ndarray是同一份内存。
所以若需要copy.
img_copy = img_src.copy()