机器视觉相关技术总结-1

  • Skimage与Opencv在处理图像时的一些基本操作对比
  • 1. 安装
  • 2. 一些小的先验知识
  • 3. 开始读取图片
  • 3.1 cv2读取图片
  • 3.2 Skimage读取图片
  • 4. 灰度图
  • 5. 翻转
  • 6. 直方图
  • 7. 过滤器
  • 7.1 skimage中封装的过滤器
  • 7.2 cv2中封装的过滤器
  • 7.3 关于全局与局部阈值
  • 8. 边缘检测
  • 9. 轮廓检测


Skimage与Opencv在处理图像时的一些基本操作对比

1. 安装

建议在windows系统下安装下面的包,实测在mac os系统下安装scikit-image包会出现诸多异常,即使安装上了在读取图片的时候会出现缺失backend的错误,目前还没有找到有效的解决方式,待更新

安装cv2: pip install opencv-python
安装skimage: pip install scikit-image
安装matplotlib: pip install matplotlib

2. 一些小的先验知识

(1) 无论是利用cv2还是skimage读取图像,其数据输出的结果都是numpy.ndarray的形式,一般是(height, width, channels)
(2) cv2读出来的图像的取值范围是0-255, 而skimage读出的图像做了归一化处理,取值范围为0-1

3. 开始读取图片

3.1 cv2读取图片

import cv2
import matplotlib.pyplot as plt

image_BGR = cv2.imread("./res/OIP-C.jpg")
# 下面两种方法均可以解决色差问题
# cv2读出来的是BGR,plt绘图是RGB
image_RGB = cv2.cvtColor(image_BGR, cv2.COLOR_BGR2RGB)
image_RGB2 = image_BGR[:,:,[2,1,0]]
print (image_RGB2.all() == image_BGR.all())

plt.imshow(image_RGB)
plt.axis("off")
plt.savefig("./res/fig-1.png")

skimage 需要python2还是python3 skimage和opencv_学习

3.2 Skimage读取图片

# 实际上它和cv2读出来的东西是一毛一样的
from skimage.io import imread

image_ski = imread("OIP-C.jpg")

4. 灰度图

: 灰度图是二维矩阵,没有第三个通道。

""" scikit-image """
from skimage.color import rgb2gray
gray_ski = rgb2gray(image_ski)  # 这里面是灰度值除以256,进行了归一化处理

""" cv2 """
gray = cv2.cvtColor(image_BGR, cv2.COLOR_BGR2GRAY)
plt.imshow(gray_ski, cmap="gray")
plt.imshow(gray, cmap="gray")

其实,从下图中可以明显看出,skimage和cv2读出来的图像亮度不是很一致,目前看主要就是因为归一化的问题,导致两个数据本身的范围有一定的差异导致的。

skimage 需要python2还是python3 skimage和opencv_opencv_02


skimage 需要python2还是python3 skimage和opencv_opencv_03

5. 翻转

""" skimage """
# 水平翻转
horizontal_flipped = np.fliplr(image_RGB)  # fliplr: flip from left to right
plt.imshow(horizontal_flipped)

# 垂直翻转
vertically_flipped = np.flipud(image_RGB)  # flipud: flip from up to down
plt.imshow(vertically_flipped)

## 水平 + 垂直
# 连续做两次变换其实也可以实现相同的事情
vertically_flipped_2 = np.flipud(image_RGB)
final_flipped = np.fliplr(vertically_flipped_2)
plt.imshow(final_flipped)

""" cv2 """
# 水平翻转
horizontal_flipped_1 = cv2.flip(image_RGB, 1)
plt.imshow(horizontal_flipped_1)

# 垂直翻转
vertically_flipped_1 = cv2.flip(image_RGB, 0)
plt.imshow(vertically_flipped_1)

## 水平 + 垂直
# 这个方法numpy也可以实现,只是cv2中有集成好的函数
hv_flipped = cv2.flip(image_RGB, -1)
plt.imshow(hv_flipped)

skimage 需要python2还是python3 skimage和opencv_像素点_04

6. 直方图

通过颜色通道的强度有助于了解颜色分布,最直观的方法就是绘制直方图

def plot_with_hist_channel(RGB_image, channel):
    
    channels = ["red", "green", "blue"]
    channel_id = channels.index(channel)
    color = channels[channel_id]
    
    extracted_channel = RGB_image[:, :, channel_id]
    
    fig, (ax1, ax2) = plt.subplots(nrows=1, ncols=2, figsize=(18, 6))
    
    ax1.imshow(RGB_image)
    ax1.axis("off")
    
    # 全展开
    ax2.hist(extracted_channel.ravel(), bins=256, color=color)
    ax2.set_title(f"{channels[channel_id]} histogram")

skimage 需要python2还是python3 skimage和opencv_封装_05

7. 过滤器

阈值分割在图像分割、目标检测以及边缘、轮廓提取等方面有广大的应用

mean_cv2 = gray.mean()
mean_ski = gray_ski.mean()
print (mean_cv2)
print (mean_ski)
print (mean_cv2 / 256)

>>> 89.86703858670386
>>> 0.35006973648371487
>>> 0.35104311947931194

均值上也可以看出两种方法存在细微的差别,但差得非常有限,嘿嘿

7.1 skimage中封装的过滤器

这里你使用cv2的读取结果或者skimage的结果都可以,只不过要注意一下阈值的取值范围

# Set threshold
threshold = 0.4
threshold_cv2 = 102
# 掩码操作,像素低于阈值则变为0(黑),反之则变为1(白)
binary_image_cv2 = gray > threshold_cv2
binary_image = gray_ski > threshold

plt.imshow(binary_image_cv2, cmap="binary")

## 逆向处理,背景被白
inverted_binary_image_cv2 = gray <= threshold_cv2
inverted_binary_image = gray_ski <= threshold

plt.imshow(inverted_binary_image_cv2, cmap="binary")

skimage 需要python2还是python3 skimage和opencv_封装_06


通常用眼来判断阈值并不是很合理,skimage的try_all_threshold函数,这里涉及了Isodata, Li, Mean, Minimum, Otsu, Triangle, Yen七种算法

## 具体代码如下:
from skimage.filters import try_all_threshold

fig, ax = try_all_threshold(gray, figsize=(10, 8), verbose=False)

# 具体算法的话,可以用一下代码
from skimage.filters import threshold_li, threshold_triangle, *

skimage 需要python2还是python3 skimage和opencv_封装_07

7.2 cv2中封装的过滤器

注意: cv2的过滤器可以针对RGB图像

cv2中内含的阈值处理方法:

  1. cv2.THRESH_BINARY 二值化阈值处理
  2. cv2.THRESH_BINARY _INV 反二值化阈值处理
  3. cv2.THRESH_TRUNC 截断阈值处理
  4. cv2.THRESH_TOZERO_INV 超阈值零处理
  5. cv2.THRESH_TOZERO 低阈值零处理
  6. cv2.THRESH_MASK 掩码处理
  7. cv2.THRESH_OTSU Otsu算法阈值处理

下面的代码就是按照上面的顺序依次编写的,自己对照一下就好了

t, rst = cv2.threshold(gray, 102, 255, cv2.THRESH_BINARY)
plt.imshow(rst, cmap="binary")

# 这个其实就是单纯的二值化操作,其与上面的手工阈值是一样的,就是封装函数不一样罢了
t, rst = cv2.threshold(gray, 102, 255, cv2.THRESH_BINARY_INV)
plt.imshow(rst, cmap="binary")

# 该方法表示:对于像素值大于阈值thresh的像素点,将其设置为阈值;
# 对于像素值小于阈值thresh的像素点,保持不变
t, rst = cv2.threshold(gray, 127, 255, cv2.THRESH_TRUNC)
plt.imshow(rst, cmap="binary")

# 该方法表示:对于像素值大于阈值thresh的像素点,将其设置为0;
# 对于像素值小于阈值thresh的像素点,保持不变。
t, rst = cv2.threshold(gray, 127, 255, cv2.THRESH_TOZERO_INV)
plt.imshow(rst, cmap="binary")

# 该方法表示:对于像素值小于或等于阈值thresh的像素点,将其设置为0;
# 对于像素值大于阈值thresh的像素点,保持不变
t, rst = cv2.threshold(gray, 102, 255, cv2.THRESH_TOZERO)
plt.imshow(rst, cmap="binary")

# 自适应阈值
## 均值算子
rst_mean = cv2.adaptiveThreshold(gray, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 5, 3)
plt.imshow(rst_mean, cmap="binary")

## 高斯算子
rst_gaus = cv2.adaptiveThreshold(gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 5, 3)
plt.imshow(rst_gaus, cmap="binary")

# Otsu方法能够根据当前图像生成最佳的类间分割阈值,原理是遍历所有可能的阈值,从而找到最合适的阈值。
t, otsu = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY+cv2.THRESH_OTSU)
plt.imshow(otsu, cmap="binary")
7.3 关于全局与局部阈值

阈值算法通常有两种类型:

  • 全局-适用于具有均匀、统一背景的照片
  • 局部-用于不同图片区域中具有不同亮度级别的图像

郁金香图像属于第二类,因为右侧部分比另一半亮得多,使其背景不均匀。我们不能在其上使用全局阈值算法,这就是为什么try_all_threshold中所有算法的性能都很差的原因

skimage 需要python2还是python3 skimage和opencv_像素点_08


局部阈值skimage实现:

# 还有一种局部的阈值筛选方法
from skimage.filters import threshold_local

local_thresh = threshold_local(gray, offset=0.0002)
binary_scene = gray > local_thresh

plt.imshow(binary_scene, cmap="binary")

结果有一码说一码挺奇怪的,只是作为一种方法供大家测试,提供一下思路

8. 边缘检测

""" skimage """
# skimage 提供的边缘检测算法
# sobel, roberts, scharr, prewitt 四种算法
from skimage.filters import sobel, roberts, scharr, prewitt

edge_roberts = roberts(gray)
edge_sobel = sobel(gray)
edge_scharr = scharr(gray)
edge_prewitt = prewitt(gray)


fig,ax = plt.subplots(nrows =2,ncols=2, sharex=True, sharey=True,
                      figsize=(10, 8))

ax[0,0].imshow(edge_roberts, cmap=plt.cm.gray)
ax[0,0].set_title('Roberts Edge Detection')
ax[0,0].axis('off')

ax[0,1].imshow(edge_sobel, cmap=plt.cm.gray)
ax[0,1].set_title('Sobel Edge Detection')
ax[0,1].axis('off')

ax[1,0].imshow(edge_scharr, cmap=plt.cm.gray)
ax[1,0].set_title('Scharr Edge Detection')
ax[1,0].axis('off')

ax[1,1].imshow(edge_prewitt, cmap=plt.cm.gray)
ax[1,1].set_title('Prewitt Edge Detection')
ax[1,1].axis('off')

plt.tight_layout()
plt.show()

skimage 需要python2还是python3 skimage和opencv_python_09


注意:我们输出一下边缘检测的结果,可以看到

print (edge_prewitt.shape)

>>> (180, 239)
""" cv2 """
# cv2封装的边缘检测包 (sobel)
# sobel得到的图像是有许多小点的的
def sobel(img):
    # 核函数的取值范围:1,3,5,7,9,核函数过大效果不好
    Ksize = 3

    sobelx = cv2.Sobel(img, cv2.CV_64F, 1, 0, ksize=Ksize)
    sobely = cv2.Sobel(img, cv2.CV_64F, 0, 1, ksize=Ksize)

    # sobel-x方向
    sobel_X = cv2.convertScaleAbs(sobelx)
    # sobel-y方向
    sobel_Y = cv2.convertScaleAbs(sobely)
    # sobel-xy方向
    sobel_XY = cv2.addWeighted(sobel_X, 0.5, sobel_Y, 0.5, 0)
    return sobel_XY

binary_sobel_cv2 = sobel(gray)
print (binary_sobel_cv2.shape)
plt.imshow(binary_sobel_cv2, 'binary')

def scharr(img):
    scharr_x = cv2.Scharr(img, cv2.CV_8U, 1, 0)
    scharr_y = cv2.Scharr(img, cv2.CV_8U, 0, 1)
    scharrX = cv2.convertScaleAbs(scharr_x)
    scharrY = cv2.convertScaleAbs(scharr_y)
    scharr_XY = cv2.addWeighted(scharrX, 0.5, scharrY, 0.5, 0)
    return scharr_XY

binary_scharr_cv2 = scharr(gray)
plt.imshow(binary_scharr_cv2, "binary")

cv2轮廓图像看起来不是很一样,这个后面会再进行相应的测试补充


9. 轮廓检测

# cv2封装的包
# contours, hierarchy=cv2.findContours(image, mode, method, contours=None, hierarchy=None, offset=None)
contour, hierarchy = cv2.findContours(otsu, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
print (hierarchy.shape)
contour_image = image_RGB.copy()
cv2.drawContours(contour_image, contour, -1, (0, 255, 0), 3)
cv2.imshow("1", contour_image)
cv2.waitKey()
cv2.destroyAllWindows()

skimage 需要python2还是python3 skimage和opencv_学习_10