Python 中带条件的逐像素操作,无缓慢循环
Per-Pixel operation with conditonals in Python without slowy loops
我正在尝试对图像应用阈值,但不是常规的简单阈值。
如果符合条件,我需要设置为黑色像素,如果不符合,则设置为白色。
我可以循环遍历像素,但在 1080p 图像上,它太长了。
我正在使用 HSV 进行我需要进行的比较。
这是条件语句(这个例子是我在循环中使用它的方式):
if abs(input_pixel_color.hue - reference.hue) < 2 and input_pixel_color.saturation >= 0.25 and input_pixel_color.brightness >= 0.42:
set_to_black
else:
set_to_white
input_pixel
是循环中像素的HSV值。
reference
是要比较的变量。
想过用numpy,但是实在不知道怎么写:/
提前致谢
已更新
既然您的实际预期处理已经变得更加清晰,那么 OpenCV inRange()
函数可能会更好地为您服务。像这样:
#!/usr/local/bin/python3
import cv2 as cv
import numpy as np
# Load the image and convert to HLS
image = cv.imread("image.jpg")
hls = cv.cvtColor(image,cv.COLOR_BGR2HLS)
# Define lower and uppper limits for each component
lo = np.array([50,0,0])
hi = np.array([70,255,255])
# Mask image to only select filtered pixels
mask = cv.inRange(hls,lo,hi)
# Change image to white where we found our colour
image[mask>0]=(255,255,255)
cv.imwrite("result.png",image)
所以,如果我们使用这张图片:
我们正在选择 50-70 范围内的色调,并将它们设为白色:
如果你去 here 颜色转换器,你可以看到 "Green" 是 Hue=120,但是 OpenCV 将 Hues 除以 2,这样360 度变为 180 度并且仍然适合 uint8。所以,我们代码中的 60 在在线颜色转换器中意味着 120。
OpenCV 用于 uint8 图像的范围是:
- 色调 0..180
- 亮度 0..255
- 饱和度 0..255
正如我之前所说,您应该养成在调试器中查看数据类型、形状和范围的习惯。要查看 shape
、dtype
和最大色相、亮度和饱和度,请使用:
print(hls.dtype, hls.shape)
print(hls[...,0].max())
print(hls[...,1].max())
print(hls[...,2].max())
原答案
有几种方法可以做到这一点。性能最高的可能是 OpenCV 函数 cv2.inRange()
,Whosebug 上有很多关于此的答案。
这是一种 Numpy 方式。如果您阅读评论并查看打印值,您可以了解如何将逻辑 AND 与逻辑 OR 等结合起来,以及如何处理特定通道。
#!/usr/bin/env python3
from random import randint, seed
import numpy as np
# Generate a repeatable random HSV image
np.random.seed(42)
h, w = 4, 5
HSV = np.random.randint(1,100,(h,w,3),dtype=np.uint8)
print('Initial HSV\n',HSV)
# Create mask of all pixels with acceptable Hue, i.e. H > 50
HueOK = HSV[...,0] > 50
print('HueOK\n',HueOK)
# Create mask of all pixels with acceptable Saturation, i.e. S > 20 AND S < 80
SatOK = np.logical_and(HSV[...,1]>20, HSV[...,1]<80)
print('SatOK\n',SatOK)
# Create mask of all pixels with acceptable value, i.e. V < 20 OR V > 60
ValOK = np.logical_or(HSV[...,2]<20, HSV[...,2]>60)
print('ValOK\n',ValOK)
# Combine masks
combinedMask = HueOK & SatOK & ValOK
print('Combined\n',combinedMask)
# Now, if you just want to set the masked pixels to 255
HSV[combinedMask] = 255
print('Result1\n',HSV)
# Or, if you want to set the masked pixels to one value and the others to another value
HSV = np.where(combinedMask,255,0)
print('Result2\n',HSV)
示例输出
Initial HSV
[[[93 98 96]
[52 62 76]
[93 4 99]
[15 22 47]
[60 72 85]]
[[26 72 61]
[47 66 26]
[21 45 76]
[25 87 40]
[25 35 83]]
[[66 40 87]
[24 26 75]
[18 95 15]
[75 86 18]
[88 57 62]]
[[94 86 45]
[99 26 19]
[37 24 63]
[69 54 3]
[33 33 39]]]
HueOK
[[ True True True False True]
[False False False False False]
[ True False False True True]
[ True True False True False]]
SatOK
[[False True False True True]
[ True True True False True]
[ True True False False True]
[False True True True True]]
ValOK
[[ True True True False True]
[ True False True False True]
[ True True True True True]
[False True True True False]]
Combined
[[False True False False True]
[False False False False False]
[ True False False False True]
[False True False True False]]
Result1
[[[ 93 98 96]
[255 255 255]
[ 93 4 99]
[ 15 22 47]
[255 255 255]]
[[ 26 72 61]
[ 47 66 26]
[ 21 45 76]
[ 25 87 40]
[ 25 35 83]]
[[255 255 255]
[ 24 26 75]
[ 18 95 15]
[ 75 86 18]
[255 255 255]]
[[ 94 86 45]
[255 255 255]
[ 37 24 63]
[255 255 255]
[ 33 33 39]]]
Result2
[[ 0 255 0 0 255]
[ 0 0 0 0 0]
[255 0 0 0 255]
[ 0 255 0 255 0]]
备注:
1) 您还可以访问未被蒙版选中的像素,使用否定:
# All unmasked pixels become 3
HSV[~combinedMask] = 3
2)省略号(...
)只是一个简写,意思是"all other dimensions I didn't bother listing",所以HSV[...,1]
和[=24是一样的=]
3) 如果你不喜欢色相写HSV[...,0]
,饱和度写HSV[...,1]
,你可以拆分通道
H, S, V = cv2.split(HSV)
那么您可以使用 H
而不是 HSV[...,0]
。完成后,如果您想将通道重新assemble 变回 3 通道图像,您可以这样做:
HSV = cv2.merge((H,S,V))
或
HSV = np.dstack((H,S,V))
我正在尝试对图像应用阈值,但不是常规的简单阈值。
如果符合条件,我需要设置为黑色像素,如果不符合,则设置为白色。
我可以循环遍历像素,但在 1080p 图像上,它太长了。
我正在使用 HSV 进行我需要进行的比较。
这是条件语句(这个例子是我在循环中使用它的方式):
if abs(input_pixel_color.hue - reference.hue) < 2 and input_pixel_color.saturation >= 0.25 and input_pixel_color.brightness >= 0.42:
set_to_black
else:
set_to_white
input_pixel
是循环中像素的HSV值。
reference
是要比较的变量。
想过用numpy,但是实在不知道怎么写:/
提前致谢
已更新
既然您的实际预期处理已经变得更加清晰,那么 OpenCV inRange()
函数可能会更好地为您服务。像这样:
#!/usr/local/bin/python3
import cv2 as cv
import numpy as np
# Load the image and convert to HLS
image = cv.imread("image.jpg")
hls = cv.cvtColor(image,cv.COLOR_BGR2HLS)
# Define lower and uppper limits for each component
lo = np.array([50,0,0])
hi = np.array([70,255,255])
# Mask image to only select filtered pixels
mask = cv.inRange(hls,lo,hi)
# Change image to white where we found our colour
image[mask>0]=(255,255,255)
cv.imwrite("result.png",image)
所以,如果我们使用这张图片:
我们正在选择 50-70 范围内的色调,并将它们设为白色:
如果你去 here 颜色转换器,你可以看到 "Green" 是 Hue=120,但是 OpenCV 将 Hues 除以 2,这样360 度变为 180 度并且仍然适合 uint8。所以,我们代码中的 60 在在线颜色转换器中意味着 120。
OpenCV 用于 uint8 图像的范围是:
- 色调 0..180
- 亮度 0..255
- 饱和度 0..255
正如我之前所说,您应该养成在调试器中查看数据类型、形状和范围的习惯。要查看 shape
、dtype
和最大色相、亮度和饱和度,请使用:
print(hls.dtype, hls.shape)
print(hls[...,0].max())
print(hls[...,1].max())
print(hls[...,2].max())
原答案
有几种方法可以做到这一点。性能最高的可能是 OpenCV 函数 cv2.inRange()
,Whosebug 上有很多关于此的答案。
这是一种 Numpy 方式。如果您阅读评论并查看打印值,您可以了解如何将逻辑 AND 与逻辑 OR 等结合起来,以及如何处理特定通道。
#!/usr/bin/env python3
from random import randint, seed
import numpy as np
# Generate a repeatable random HSV image
np.random.seed(42)
h, w = 4, 5
HSV = np.random.randint(1,100,(h,w,3),dtype=np.uint8)
print('Initial HSV\n',HSV)
# Create mask of all pixels with acceptable Hue, i.e. H > 50
HueOK = HSV[...,0] > 50
print('HueOK\n',HueOK)
# Create mask of all pixels with acceptable Saturation, i.e. S > 20 AND S < 80
SatOK = np.logical_and(HSV[...,1]>20, HSV[...,1]<80)
print('SatOK\n',SatOK)
# Create mask of all pixels with acceptable value, i.e. V < 20 OR V > 60
ValOK = np.logical_or(HSV[...,2]<20, HSV[...,2]>60)
print('ValOK\n',ValOK)
# Combine masks
combinedMask = HueOK & SatOK & ValOK
print('Combined\n',combinedMask)
# Now, if you just want to set the masked pixels to 255
HSV[combinedMask] = 255
print('Result1\n',HSV)
# Or, if you want to set the masked pixels to one value and the others to another value
HSV = np.where(combinedMask,255,0)
print('Result2\n',HSV)
示例输出
Initial HSV
[[[93 98 96]
[52 62 76]
[93 4 99]
[15 22 47]
[60 72 85]]
[[26 72 61]
[47 66 26]
[21 45 76]
[25 87 40]
[25 35 83]]
[[66 40 87]
[24 26 75]
[18 95 15]
[75 86 18]
[88 57 62]]
[[94 86 45]
[99 26 19]
[37 24 63]
[69 54 3]
[33 33 39]]]
HueOK
[[ True True True False True]
[False False False False False]
[ True False False True True]
[ True True False True False]]
SatOK
[[False True False True True]
[ True True True False True]
[ True True False False True]
[False True True True True]]
ValOK
[[ True True True False True]
[ True False True False True]
[ True True True True True]
[False True True True False]]
Combined
[[False True False False True]
[False False False False False]
[ True False False False True]
[False True False True False]]
Result1
[[[ 93 98 96]
[255 255 255]
[ 93 4 99]
[ 15 22 47]
[255 255 255]]
[[ 26 72 61]
[ 47 66 26]
[ 21 45 76]
[ 25 87 40]
[ 25 35 83]]
[[255 255 255]
[ 24 26 75]
[ 18 95 15]
[ 75 86 18]
[255 255 255]]
[[ 94 86 45]
[255 255 255]
[ 37 24 63]
[255 255 255]
[ 33 33 39]]]
Result2
[[ 0 255 0 0 255]
[ 0 0 0 0 0]
[255 0 0 0 255]
[ 0 255 0 255 0]]
备注:
1) 您还可以访问未被蒙版选中的像素,使用否定:
# All unmasked pixels become 3
HSV[~combinedMask] = 3
2)省略号(...
)只是一个简写,意思是"all other dimensions I didn't bother listing",所以HSV[...,1]
和[=24是一样的=]
3) 如果你不喜欢色相写HSV[...,0]
,饱和度写HSV[...,1]
,你可以拆分通道
H, S, V = cv2.split(HSV)
那么您可以使用 H
而不是 HSV[...,0]
。完成后,如果您想将通道重新assemble 变回 3 通道图像,您可以这样做:
HSV = cv2.merge((H,S,V))
或
HSV = np.dstack((H,S,V))