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

正如我之前所说,您应该养成在调试器中查看数据类型、形状和范围的习惯。要查看 shapedtype 和最大色相、亮度和饱和度,请使用:

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))