机器视觉相关技术总结-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")
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读出来的图像亮度不是很一致,目前看主要就是因为归一化的问题,导致两个数据本身的范围有一定的差异导致的。
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)
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")
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的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, *
7.2 cv2中封装的过滤器
注意: cv2的过滤器可以针对RGB图像
cv2中内含的阈值处理方法:
- cv2.THRESH_BINARY 二值化阈值处理
- cv2.THRESH_BINARY _INV 反二值化阈值处理
- cv2.THRESH_TRUNC 截断阈值处理
- cv2.THRESH_TOZERO_INV 超阈值零处理
- cv2.THRESH_TOZERO 低阈值零处理
- cv2.THRESH_MASK 掩码处理
- 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实现:
# 还有一种局部的阈值筛选方法
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()
注意:我们输出一下边缘检测的结果,可以看到
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()