Select Language

AI社区

数据要素产业

OpenCV 指南2:如何在图像中进行边缘检测?

12-08 00:30 TAG: OpenCV 图像处理

在上篇文章中,我们已经学习了 OpenCV 的基础知识。我们已经看到了如何执行图像和视频调整大小、裁剪等,这是 OpenCV 教程的第二部分。

本文,我们主要关注 OpenCV 的高级部分,它将涵盖以下提到的问题。但是,如果你是 OpenCV 的新手或觉得这很难理解,那么请访问OpenCV 基础指南第1部分并复习基础知识,然后再回到此文。

我们思考的问题是:

如何在图像中进行边缘检测?

如何在图像中进行轮廓检测?

如何在图像中执行颜色空间?

如何处理颜色通道?

如何模糊图像?

如何使用位运算符?

如何在图像的每个像素点绘制颜色的直方图?

1)如何在图像中进行边缘检测?

1.1. Canny:对于这里的边缘检测,我们将使用 cv.Canny 方法。输入图像为原始图像,thereshold-1 即像素值低于150 被视为非边缘,threshold-2 即像素值高于175 被视为有效边缘。如果该值在 150 和 175 之间,那么如果边缘像素与有效边缘相连,则仅将其视为有效边缘。original_img=cv.imread("/content/drive/MyDrive/Colab Notebooks/Opencv/NCS/hana-lshin-qUhu8zjm38w-unsplash(1).jpg")
cv2_imshow(original_img)
print("  Edge detection in the Original images  ")
#We will find the edges in the image as below
Edge_Org=cv.Canny(original_img,150,175)
cv2_imshow(Edge_Org)
上述代码的输出:原图:

原始图像的边缘:

现在我们将尝试通过使其模糊来找到原始图像的边缘。博客中进一步介绍了如何使图像模糊。

通过比较原始图像和模糊图像之间的边缘检测。我们知道图像中的边缘检测随着图像模糊程度的增加而减少。1.2. 拉普拉斯算子在拉普拉斯边缘检测方法中,我们将计算源图像的 (x,y) 像素的第二个梯度。当 ksize>1 时,查看下面提到的源图像的拉普拉斯公式:

当 ksize=1 时,拉普拉斯算子是通过使用以下 3×3 孔径过滤图像来计算:

其中 ksize:用于计算二阶导数滤波器的孔径大小。大小必须是正数和奇数。lap=cv.Laplacian(original_img,cv.CV_64F)
lap=np.uint8(np.absolute(lap))
cv2_imshow(lap)
上述代码的输出:拉普拉斯边缘检测:

1.3.Sobel边缘检测在计算拉普拉斯算子时,我们计算了称为 Sobel 的二阶导数。因此,在 sobel 检测中,我们将计算 sobelx(也称为水平 Sobel 导数)和 sobely(也称为垂直 Sobel 导数)。我们可以通过输入图像与大小为 3*3 的内核进行卷积,来计算 Sobelx 和 sobely(但我们可以根据需要更改内核大小)。拉普拉斯公式中的 Sobelx 和 sobely:

提到的卷积的 3*3 矩阵是:

Sobel 边缘检测的代码:#Sobel
Sobelx=cv.Sobel(Gray_1,cv.CV_64F,1,0)
Sobely=cv.Sobel(Gray_1,cv.CV_64F,0,1)
Cpmbine_Sobel=cv.bitwise_or(Sobelx,Sobely)
print(" SobelX Edge Detection")
cv2_imshow(Sobelx)
print(" Sobely Edge Detection")
cv2_imshow(Sobely)
print(" ombine Sobel Edge Detection")
cv2_imshow(Cpmbine_Sobel)
上述代码的输出:Sobelx:

Sobely:

结合Sobel:

1.4. 腐蚀和膨胀

1.4.1 腐蚀它对于去除小的白噪声很有用。用于分离两个连接的对象等。怎么运作:内核(奇数大小的矩阵(3,5,7)与图像卷积。仅当内核下的所有像素都为 1 时,原始图像中的像素(1 或 0)才会被视为 1,否则它会被腐蚀(使其为零)。因此,根据内核的大小,将丢弃边界附近的所有像素。因此,前景对象的厚度或大小会减少,或者只是图像中的白色区域会减少。1.4.2 膨胀:在去除噪声等情况下,腐蚀之后是膨胀。因为,腐蚀去除了白噪声,但它也缩小了我们的对象。所以我们膨胀它。由于噪音消失了,它们不会回来,但我们的对象区域会增加。它也可用于连接对象的损坏部分。这个怎么运作:内核(奇数大小的矩阵(3,5,7)与图像卷积如果内核下至少有一个像素为“1”,则原始图像中的像素元素为“1”。它增加了图像中的白色区域或前景对象的大小增加Dilate 和 Erode 的代码演练:#Dilating the image i.e., It will incrases the thickness of the edges
Dilat_img=cv.dilate(Edge_blur,(7,7),iterations=3)
cv2_imshow(Dilat_img)
#We can restore to the original edges by using the erode
print(" ")
Erode_img=cv.erode(Dilat_img,(7,7),iterations=3)
cv2_imshow(Erode_img)
上述代码的输出:将图像输入到 cv.dilate():

Dilate : 它会增加边缘的厚度

腐蚀:这里的输入图像是 cv.dilate 输出。所以腐蚀输出将恢复 Dilate 的输入图像。

2)如何在图像中进行轮廓检测?

有时,如果我们对图像执行边缘检测和轮廓检测,则两者的输出可能看起来相似。但与边缘检测相比,轮廓检测通常会更详细。cv.findContours 函数中的一些重要参数是:Image :输入图像应该是二进制作为源,一个8位的单通道图像。非零像素被视为 1。零像素保持 0,因此图像被视为二进制。轮廓检索模式:1)cv.RETR_TREE →它将给出图像中的分层轮廓。cv.RETR_EXTERNAL →它将给出图像中唯一的外部轮廓。cv.RETR_LIST →它将给出图像中存在的所有轮廓。轮廓逼近方法:1)CHAIN_APPROX_NONE →它将给出图像中存在的所有计数器。2)CHAIN_APPROX_SIMPLE →它将给出图像中的重要轮廓,例如,如果我们有线,它只会给出图像中的起点和终点,而在 CHAIN_APPROX_NONE 的情况下,我们得到所有点。现在让我们来看看它实际上是如何工作的。Org_img=cv.imread("/content/drive/MyDrive/Colab Notebooks/Opencv/NCS/olena-sergienko-UQLGR8otAEs-unsplash(1).jpg")
Canny_img=cv.Canny(Org_img,100,150)
cv2_imshow(Canny_img)
contours,hierarchies= cv.findContours(Canny_img,cv.RETR_LIST,cv.CHAIN_APPROX_NONE)
print(" Number of the contours in the image: ",len(contours))
上图的输出:

现在我们将模糊图像并查看轮廓的数量blur_img=cv.GaussianBlur(Org_img,(5,5),cv.BORDER_DEFAULT)
#if image has the pixel <150 then it will consider as the 0(black) and if >175 then it is consider as the 255(white)
blur_Canny=cv.Canny(blur_img,150,175)
cv2_imshow(blur_Canny)
# As we have the blur image edges as a Input to the findContours so the number of the contours is also decreases.
contours,hierarchies= cv.findContours(blur_Canny,cv.RETR_LIST,cv.CHAIN_APPROX_NONE)
print(" Number of the contours in the image: ",len(contours))
上述代码的输出:

通过观察图像和它的总轮廓,我们知道模糊图像中的轮廓数量比原始图像少。现在我们将尝试在空白图像上绘制图像的轮廓cv2_imshow(Canny_img)
contours,hierarchies= cv.findContours(Canny_img,cv.RETR_LIST,cv.CHAIN_APPROX_SIMPLE)
print(len(contours))
print(len(hierarchies))
Blank_contours=np.zeros((600,600,3),dtype='uint8')
print(" Plotting the Countours on the blank")
cv.drawContours(Blank_contours,contours,-1,(0,0,255),1)
cv2_imshow(Blank_contours)
上述代码的输出:

在空白图像上绘制轮廓:

3)如何在图像中执行颜色空间?

我们知道,每一种颜色都是红、绿、蓝三种颜色的组合。所以我们通常将颜色空间称为RGB,如果我们分析图像和视频也是如此。但是在 OpenCV 中,颜色空间是相反的顺序,即 BGR。查看下面提到的代码,我们将在其中使用 OpenCV 和 matplotlib 显示图像并观察更改。cv2_imshow(Org_img)
plt.imshow(Org_img)
使用 OpenCV 的 BGR 图像:

我们将尝试使用 matplotlib 绘制相同的图像

所以这里我们观察到,由于从 BGR 到 RGB 的颜色空间发生了变化,因此颜色发生了反向变化。现在我们将查看如何使用 cv.cvtColor() 方法将 BGR 颜色空间转换为不同的颜色空间3.1)BGR转灰色# BGR to Gray
BGR_Gray=cv.cvtColor(Org_img,cv.COLOR_BGR2GRAY)
cv2_imshow(BGR_Gray)
输出:

3.2) BGR 到 HSV:print("")
#BGR to HSV
BGR_HSV=cv.cvtColor(Org_img,cv.COLOR_BGR2HSV)
cv2_imshow(BGR_HSV)
输出:

3.3) BGR 到 LAB:print("")
#BGR to LAB
BGR_LAB=cv.cvtColor(Org_img,cv.COLOR_BGR2LAB)
cv2_imshow(BGR_LAB)
输出:

3.4)BGR转RGB:print("")
#BGR to RGB
BGR_RGB=cv.cvtColor(Org_img,cv.COLOR_BGR2RGB)
cv2_imshow(BGR_RGB)
输出:

我们还可以使用下面提到的颜色空间参数来反转上面提到的图像颜色空间:cv.COLOR_HSV2BGRcv.COLOR_LAB2BGRcv.COLOR_Gray2BGRcv.COLOR_BGR2RGB4)如何处理颜色通道?在OpenCV 中,我们可以从原始图像中分离 B、G、R 通道,然后再次合并所有 B、G、R,如下所示。Org_img=cv.imread("/content/drive/MyDrive/Colab Notebooks/Opencv/NCS/erik-mclean-jhNwxqL51xc-unsplash.jpg")
cv2_imshow(Org_img)
Canny_img=cv.Canny(Org_img,100,150)
B,G,R=cv.split(Org_img)
cv2_imshow(B)
print("")
cv2_imshow(G)
print("")
cv2_imshow(R)
print("")
print("Shape of the original BGR image: ",Org_img.shape)
print("Shape of the Blue Pixel image post split from original image: ",B.shape)
print("Shape of the Green Pixel image post split from original image: ",G.shape)
print("Shape of the Red Pixel image post split from original image: ",R.shape)
print("")
print("Merged image of the BGR images is: ")
print("")
Merged_BGR=cv.merge([B,G,R])
cv2_imshow(Merged_BGR)
上述代码的输出:原图:

带有蓝色像素的图像:

带有绿色像素的图像:

带有红色像素的图像:

合并所有三个 BGR 频道的图片发布:

上述分割图像以灰色图像的形式反映,因此为了清楚了解每个通道,我们将尝试在空白屏幕上绘制此分割图像,然后查看通道。现在,我们将 B、G、R 像素与空白图像分开使用,我们得到了下面的输出:print(" Printing the BGR images on the blank images")
blank=np.zeros(((Org_img.shape[0],Org_img.shape[1])),dtype='uint8')
Merged_B=cv.merge([B,blank,blank])
cv2_imshow(Merged_B)
print("")
Merged_G=cv.merge([blank,G,blank])
cv2_imshow(Merged_G)
print("")
Merged_R=cv.merge([blank,blank,R])
cv2_imshow(Merged_R)
上述代码的输出:蓝色通道:

绿色通道:

红色通道:

5)如何模糊图像?

有不同的方法来模糊图像。查看下面提到的代码演练以获取详细信息:平均模糊:在这里,我们将输入原始图像并与内核进行卷积。随着内核大小的增加,图像的模糊度也会增加。高斯模糊:在这里,我们将输入原始图像并与内核进行卷积。随着内核大小的增加,图像的模糊度也会增加。

我们还应该指定 X 和 Y 方向的标准偏差,分别为 sigmaX 和 sigmaY。如果只指定了 sigmaX,则 sigmaY 与 sigmaX 相同。如果两者都为零,则根据内核大小计算它们。高斯模糊对于从图像中去除高斯噪声非常有效。中值模糊:函数cv.medianBlur()取内核区域下所有像素的中值,并将中心元素替换为该中值。这对于图像中的椒盐噪声非常有效。在这里,我们将输入原始图像并与内核进行卷积。

随着内核大小的增加,图像的模糊度也会增加。但是这里的内核大小是一维形状,而不是像平均和高斯那样的二维形状。双边模糊:cv.bilateralFilter()在保持边缘锐利的同时去除噪音非常有效。但与其他过滤器相比,操作速度较慢。我们已经看到高斯滤波器获取像素周围的邻域并找到其高斯加权平均值。这个高斯滤波器是单独的空间函数,即滤波时考虑附近的像素。

它不考虑像素是否具有几乎相同的强度。它不考虑像素是否是边缘像素。所以它也模糊了边缘,这是我们不想做的。在这里,我们将输入原始图像并与内核进行卷积。随着内核大小的增加,图像的模糊度也会增加。但是这里的内核大小是 1D 形状,而不是像平均模糊和高斯模糊那样的 2D 形状。此外,为了简单起见,我们可以将 2 个 sigma 值设置为相同。Org_img=cv.imread("/content/drive/MyDrive/Colab Notebooks/Opencv/NCS/emma-shappley-S0zmYpRTZbA-unsplash.jpg")
cv2_imshow(Org_img)
print(" Averaging blur image ")
#Averaging
AvgBlur_img=cv.blur(Org_img,(7,7))
cv2_imshow(AvgBlur_img)
print(" Gaussian blur image ")
#Gaussian Blur
GaussBlur_img=cv.GaussianBlur(Org_img,(7,7),0)
cv2_imshow(GaussBlur_img)
print(" Median blur image ")
#MedianBlur
MedianBlur_img=cv.medianBlur(Org_img,7)
cv2_imshow(MedianBlur_img)
print(" Billateral blur image ")
#Billateral Blur
BillateralBlur_img=cv.bilateralFilter(Org_img,5,50,50)
cv2_imshow(BillateralBlur_img)
上述代码的输出:原图:

平均模糊图像:

高斯模糊图像:

中值模糊图像:

双边模糊图像:

6) 如何使用位运算符?

常用的位运算符有 AND、OR、XOR 和 NOT。**BITWISE AND:**位与,它只会显示输入图像的相交像素查看下面提到的代码Org_img_1=cv.imread("/content/drive/MyDrive/Colab Notebooks/Opencv/NCS/emma-shappley-S0zmYpRTZbA-unsplash.jpg")
cv2_imshow(Org_img_1)
print("")
Org_img_2=cv.imread("/content/drive/MyDrive/Colab Notebooks/Opencv/NCS/olena-sergienko-UQLGR8otAEs-unsplash(1).jpg")
cv2_imshow(Org_img_2)
print("")
#BITWISE AND
BITWISE_AND=cv.bitwise_and(Org_img_1,Org_img_2)
cv2_imshow(BITWISE_AND)
上述代码的输出:org_img_1:

org_img_2:

BITWISE_AND:

BITWISE OR:位或,它将显示图像的相交和非相交像素。查看下面提到的代码#BITWISE OR
BITWISE_OR=cv.bitwise_or(Org_img_1,Org_img_2)
cv2_imshow(BITWISE_OR)
上述代码的输出:

BITWISE XOR:按位异或,它将显示图像的非相交像素。查看下面提到的代码#BITWISE XOR
BITWISE_XOR=cv.bitwise_xor(Org_img_1,Org_img_2)
cv2_imshow(BITWISE_XOR)
上述代码的输出:

BITWISE NOT:按位取非,即,它将反转图像的色彩空间。查看下面提到的代码#BITWISE NOT
BITWISE_NOT=cv.bitwise_not(Org_img_1)
cv2_imshow(BITWISE_NOT)
上述代码的输出:

7) 如何在图像的每个像素中绘制颜色的直方图?

7.1. BGR图像:我们将尝试使用 matplotlib 为BGR所有三个通道 绘制直方图。查看下面提到的代码和输出的详细信息:cv2_imshow(Org_img_2)
BGR_Co=('b','g','r')
for i,j in enumerate(BGR_Co):
 Org_img_Histo=cv.calcHist([Org_img_2],[i],None,[256],[0,256])
 plt.plot(Org_img_Histo,color=j)
 plt.xlim([0,256])
plt.xlabel("Bins")
plt.ylabel("# of pixels")
plt.title("Org_img Histogram")
plt.show()
上述代码的输出:BGR图像:

直方图:

7.2. 灰度图像:我们将原始图像转换为灰度图像,然后尝试绘制该图像像素的直方图。查看下面提到的代码和输出的详细信息:gray=cv.cvtColor(Org_img_2,cv.COLOR_BGR2GRAY)
cv2_imshow(gray)
Gray_Histo=cv.calcHist([gray],[0],None,[256],[0,256])
plt.figure()
plt.title("GrayScale Histogram")
plt.xlabel("Bins")
plt.ylabel("# of pixels")
plt.plot(Gray_Histo)
plt.show()
上述代码的输出:灰度图像:

直方图:

总结现在我们已经完成了OpenCV 的基础知识和高级内容,本文涵盖了 OpenCV 中的一些高级内容,这些内容主要用于计算机视觉任务。

image.png