介绍
在这篇文章中,我们将学习如何执行图像处理。在整篇文章中,我们使用到的库是Scikit Image。
1、什么是图像?
图像数据可能是文本之后最常见的数据。那么,电脑如何理解你在埃菲尔铁塔前的自拍呢?
它使用一个称为像素的小正方形网格。像素覆盖一个小区域,并具有表示颜色的值。图像中的像素越多,其质量越高,存储所需的内存越多。
就是这样。图像处理主要是处理这些单独的像素(有时是像素组),以便计算机视觉算法可以从中提取更多信息。
2、NumPy和Skimage的图像基础
在Matplotlib和Skimage中,图像都作为NumPy ndarray加载。
- from skimage.io import imread # pip install scikit-image
-
- image = imread("images/colorful_scenery.jpg")
-
- >>> type(image)
- numpy.ndarray
NumPy数组带来灵活性、速度和力量。图像处理也不例外。
Ndarrays可以轻松检索图像的一般详细信息,例如图像的尺寸:
- >>> image.shape
- (853, 1280, 3)
-
- >>> image.ndim
- 3
-
- # The number of pixels
- >>> image.size # 853 * 1280 * 3
- 3275520
我们的图像高度为853像素,宽度为1280像素。第三维表示RGB(红、绿、蓝)颜色通道的值。最常见的图像格式是3D。
你可以通过常规NumPy索引检索单个像素值。下面,我们尝试索引图像以检索三个颜色通道中的每一个通道:
- red = image[:, :, 0]
-
- compare(image, red, "Red Channel of the Image", cmap_type="Reds_r")
- green = image[:, :, 1]
-
- compare(image, green, "Green Channel of the Image", "Greens_r")
- blue = image[:, :, 2]
-
- compare(image, blue, "Blue Channel of the Image", "Blues_r")
0表示红色,1表示绿色,2表示蓝色通道-非常简单。
创建了两个函数,show和compare,它们显示一个图像或并排显示其中两个进行比较。在整个教程中,我们将广泛使用这两个函数。
按照约定,ndarray的第三维用于颜色通道,但并不总是遵循此约定。Skimage通常提供参数来指定这种行为。
图像与通常的Matplotlib绘图不同。它们的原点不位于左下角,而是位于左上角的位置(0,0)。
>>> show(image, axis=True)
当我们在Matplotlib中绘制图像时,轴表示像素的顺序,但我们通常会隐藏它们。
3、常见转换
我们将要执行的最常见的图像转换是将彩色图像转换为灰度。许多图像处理算法需要灰度图像。因为颜色不是图片的定义特征,没有它,计算机仍然可以提取足够的信息。
- from skimage.color import rgb2gray
-
- image = imread("images/grayscale_example.jpg")
- # Convert image to grayscale
- gray = rgb2gray(image)
-
- compare(image, gray, "Grayscale Image")
- >>> gray.shape
-
- (853, 1280)
当将图像转换为灰度时,它们会丢失其第三维度-颜色通道。相反,图像数组中的每个单元格现在表示uint8类型的整数。它们的范围从0到255,提供256种灰度。
你还可以使用np.flipud或者np.fliplr之类的NumPy函数,随心所欲地以任何方式操纵图像。
- kitten = imread("images/horizontal_flip.jpg")
- horizontal_flipped = np.fliplr(kitten)
-
- compare(kitten, horizontal_flipped, "Horizontally Flipped Image")
- ball = imread("images/upside_down.jpg")
-
- vertically_flipped = np.flipud(ball)
-
- compare(ball, vertically_flipped, "Vertically Flipped Image")
在“颜色”模块中,你可以找到许多其他变换函数来处理图像中的颜色。
4、颜色通道直方图
有时,查看每个颜色通道的强度有助于了解颜色分布。我们可以通过切片每个颜色通道并绘制它们的直方图来实现这一点。以下是执行此操作的函数:
- def plot_with_hist_channel(image, channel):
-
- channels = ["red", "green", "blue"]
- channel_idx = channels.index(channel)
- color = channels[channel_idx]
-
- extracted_channel = image[:, :, channel_idx]
-
- fig, (ax1, ax2) = plt.subplots(
- ncols=2, figsize=(18, 6)
- )
-
- ax1.imshow(image)
- ax1.axis("off")
-
- ax2.hist(extracted_channel.ravel(), bins=256, color=color)
-
- ax2.set_title(f"{channels[channel_idx]} histogram")
除了Matplotlib的一些细节之外,你还应该注意hist函数的调用。提取颜色通道及其数组后,我们将其展平为1D数组,并将其传递给hist函数。
bin数量应该是256个,每个像素值对应一个-0表示黑色,255表示完全白色。
让我们使用彩色风景图像:
- colorful_scenery = imread("images/colorful_scenery.jpg")
-
- plot_with_hist_channel(colorful_scenery, "red")
>>> plot_with_hist_channel(colorful_scenery, "green")
>>> plot_with_hist_channel(colorful_scenery, "blue")
还可以使用直方图在将图像转换为灰度后找出图像中的亮度:
- gray_color_scenery = rgb2gray(colorful_scenery)
-
- plt.hist(gray_color_scenery.ravel(), bins=256);
大多数像素的值较低,因为景物图像较暗。
我们将在以下部分探讨直方图的更多应用。
1、手动阈值
现在,我们来看看有趣的东西——过滤图像。我们将学习的第一个操作是阈值化。让我们加载一个示例图像:
- stag = imread("images/binary_example.jpg")
-
- >>> show(stag)
阈值分割在图像分割、目标检测、边缘或轮廓提取等方面有着广泛的应用,它主要用于区分图像的背景和前景。
阈值处理在高对比度灰度图像上效果最好:
- # Convert to graysacle
- stag_gray = rgb2gray(stag)
-
- >>> show(stag_gray)
我们将从基本的手动阈值设置开始,然后转到自动阈值设置。
首先,我们查看灰度图像中所有像素的平均值:
- >>> stag_gray.mean()
-
- 0.20056262759859955
请注意,通过将所有灰度图像的值除以256,上述灰度图像的像素在0和1之间归一化。
我们得到的平均值为0.2,这为我们提供了可能要使用的阈值的初步想法。
现在,我们使用这个阈值来进行掩码操作。如果像素值低于阈值,否则其值将变为0-黑色或1-白色。换句话说,我们得到一个黑白二值图像:
- # Set threshold
- threshold = 0.35
- # Binarize
- binary_image = stag_gray > threshold
-
- compare(stag, binary_image, "Binary image")
在这个版本中,我们可以更清楚地区分鹿的轮廓。我们可以反转遮罩,使背景变为白色:
- inverted_binary = stag_gray <= threshold
-
- >>> compare(stag, inverted_binary, "Binary image inverted")
2、阈值-全局
虽然尝试不同的阈值并观察它们对图像的影响可能很有趣,但我们通常使用比我们的眼球估计更稳健的算法来执行阈值分割。
有很多阈值算法,所以可能很难选择一种。在这种情况下,skimage具有try_all_threshold函数,该函数在给定的灰度图像上运行七种阈值算法。让我们加载一个示例并进行转换:
- flower = imread("images/global_threshold_ex.jpg")
-
- flower_gray = rgb2gray(flower)
-
- compare(flower, flower_gray)
我们将看看是否可以使用阈值优化郁金香的特征:
- from skimage.filters import try_all_threshold
-
- fig, ax = try_all_threshold(
- flower_gray, figsize=(10, 8), verbose=False
- )
正如你所看到的,一些算法在这张图像上工作得更好,而其他算法则很糟糕。otsu算法看起来更好,所以我们将继续使用它。
在这一点上,我想提请你注意郁金香的原始图像:
>>> show(flower)
图像背景不均匀,因为有太多光线从后面的窗口射进来。我们可以通过绘制灰色郁金香的直方图来证实这一点:
>>> plt.hist(flower_gray.ravel(), bins=256);
正如预期的那样,大多数像素的值都位于直方图的远端,这证实了它们大部分都是明亮的。
为什么这很重要?根据图像的亮度,阈值算法的性能也会发生变化。因此,阈值算法通常有两种类型:
全局-适用于具有均匀、统一背景的照片
局部-用于不同图片区域中具有不同亮度级别的图像。
郁金香图像属于第二类,因为右侧部分比另一半亮得多,使其背景不均匀。我们不能在其上使用全局阈值算法,这就是为什么try_all_threshold中所有算法的性能都很差的原因。
稍后我们将回到郁金香示例和局部阈值。现在,我们将加载另一个亮度更精确的实例,并尝试自动设置阈值:
- spiral = imread("images/otsu_example.jpg")
- spiral_gray = rgb2gray(spiral)
-
- compare(spiral, spiral_gray)
我们将在Skimage中使用通用的全局阈值算法threshold_otsu:
- from skimage.filters import threshold_otsu
-
- # Find optimal threshold with `threshold_otsu`
- threshold = threshold_otsu(spiral_gray)
-
- # Binarize
- binary_spiral = spiral_gray > threshold
-
- compare(spiral, binary_spiral, "Binarized Image w. Otsu Thresholding")
它工作得更好!
3、阈值-局部
现在,我们将使用局部阈值算法。
局部算法不关注整个图像,而是关注像素邻域,以解释不同区域的亮度不均匀。skimage中常见的局部算法为threshold_local函数:
- from skimage.filters import threshold_local
-
- local_thresh = threshold_local(flower_gray, block_size=3, offset=0.0002)
-
- binary_flower = flower_gray > local_thresh
-
- compare(flower, binary_flower, "Tresholded flower image")
你必须使用offset参数来找到符合你需要的最佳图像。offset是从局部像素邻域的平均值中减去的常数。该“像素邻域”由local_threshold中的block_size参数确定,该参数表示算法在每个方向上围绕每个点查看的像素数。
显然,同时调整offset和block_size是一个缺点,但局部阈值是唯一比手动或全局阈值产生更好结果的选项。
让我们再举一个例子:
- from skimage.filters import threshold_local
-
- handwriting = imread("images/chalk_writing.jpg")
- handwriting_gray = rgb2gray(handwriting)
-
- # Find optimal threshold using local
- local_thresh = threshold_local(handwriting_gray, offset=0.0003)
-
- # Binarize
- binary_handwriting = handwriting_gray > local_thresh
-
- compare(handwriting, binary_handwriting,
- "Binarized image with local thresholding")
正如你所看到的,经过阈值处理后,黑板上的笔迹更加精细。
4、边缘检测
边缘检测在很多方面都很有用,例如识别对象、从中提取特征、对其进行计数等等。
我们将从基本的Sobel滤波器开始,它在灰度图像中查找对象的边缘。我们将加载一张硬币图片,并对其使用Sobel滤波器:
- from skimage.filters import sobel
-
- coins = imread("images/coins_2.jpg")
- coins_gray = rgb2gray(coins)
-
- coins_edge = sobel(coins_gray)
-
- compare(coins, coins_edge, "Images of coins with edges detected")
sobel很直截了当;你只需在灰色图像上调用它即可获得如上所述的输出。我们将在后面的部分中看到Sobel的更复杂版本。
5、平滑
另一种图像过滤技术是平滑。许多像下面的鸡一样的图像可能包含随机噪声,而对ML和DL算法没有任何有价值的信息。
例如,鸡周围的毛发会给图像添加噪声,这可能会使ML模型的注意力偏离主要对象本身。在这种情况下,我们使用平滑来模糊噪声或边缘并降低对比度。
- chickens = imread("images/chickens.jpg")
-
- >>> show(chickens)
高斯平滑是最流行和最强大的平滑技术之一:
- from skimage.filters import gaussian
-
- smoothed = gaussian(chickens, multichannel=True, sigma=2)
-
- compare(chickens, smoothed, "An image smoothed with Gaussian smoothing")
你可以通过调整sigma参数来控制模糊的效果。如果你正在处理RGB图像,请不要忘记将multichannel设置为True。
如果图像分辨率太高,平滑效果可能肉眼看不到,但仍然有效。
6、对比度增强
某些类型的图像(如医学分析结果)对比度较低,很难发现细节,如下所示:
xray = imread("images/xray.jpg") xray_gray = rgb2gray(xray) compare(xray, xray_gray)
在这种情况下,我们可以使用对比度增强使细节更加清晰。有两种对比度增强算法:
对比度拉伸
直方图均衡化
我们将在这篇文章中讨论直方图均衡化,它又有三种类型:
标准直方图均衡化
自适应直方图均衡化
对比度受限自适应直方图均衡化(CLAHE)
直方图均衡化将图像对比度最高的区域扩展到亮度较低的区域,使其均衡。
你可以通过从最高的像素值中减去最低的像素值来计算图像的对比度。
>>> xray.max() - xray.min() 255
现在,让我们尝试exposure模块中的标准直方图均衡化:
from skimage.exposure import equalize_hist enhanced = equalize_hist(xray_gray) >>> compare(xray, enhanced)
我们已经可以更清楚地看到细节了。
from skimage.exposure import equalize_hist enhanced = equalize_hist(xray_gray) >>> compare(xray, enhanced)
接下来,我们将使用CLAHE,它为图像中的不同像素邻域计算许多直方图,即使在最暗的区域也会得到更详细的信息:
from skimage.exposure import equalize_adapthist # Adjust clip_limit enhanced_adaptive = equalize_adapthist(xray_gray, clip_limit=0.4) compare(xray, enhanced_adaptive, "Image with contrast enhancement")
这个看起来好多了,因为它可以在背景中显示细节,在左下角显示更多缺失的肋骨。你可以调整clip_limit以获得更多或更少的细节。
7、变换
数据集中的图像可能有几个相互冲突的特征,如不同的比例、未对齐的旋转等。ML和DL算法希望你的图片具有相同的形状和尺寸。因此,你需要学习如何修复它们。
旋转
要旋转图像,请使用“transform”模块中的“rotate”函数。
from skimage.transform import rotate clock = imread("images/clock.jpg") clockwise = rotate(clock, angle=-60) compare(clock, clockwise, "Clockwise rotated image, use negative angles")
anti_clockwise = rotate(clock, angle=33) compare(clock, anti_clockwise, "Anticlockwise rotated image, use positive angles")
缩放
另一个标准操作是缩放图像。
我们对此操作使用rescale函数:
butterflies = imread("images/butterflies.jpg") >>> butterflies.shape (720, 1280, 3)
from skimage.transform import rescale scaled_butterflies = rescale(butterflies, scale=3 / 4, multichannel=True) compare( butterflies, scaled_butterflies, "Butterflies scaled down by a factor of 3/4", axis=True, )
当图像分辨率较高时,缩小可能会导致质量损失或像素不协调,从而产生意外的边或角。要考虑这种影响,可以将anti_aliasing设置为True,它使用高斯平滑:
https://gist.github.com/f7ae272b6eb1bce408189d8de2b71656
与之前一样,平滑效果并不明显,但在更细粒度的级别上会更明显。
调整大小
如果希望图像具有特定的宽度和高度,而不是按系数缩放,可以通过提供output_shape来使用resize函数:
from skimage.transform import resize puppies = imread("images/puppies.jpg") # Also possible to set anti_aliasing puppies_600_800 = resize(puppies, output_shape=(600, 800)) compare(puppies, puppies_600_800, "Puppies image resized 600x800 (height, width)")
在文件变换、错误下载或许多其他情况下,某些图像可能会失真、损坏或丢失。
在本节中,我们将讨论一些图像恢复技术,从修复开始。
1、修补
修复算法可以智能地填补图像中的空白。我找不到损坏的图片,因此我们将使用此鲸鱼图像并手动在其上放置一些空白:
whale_image = imread("images/00206a224e68de.jpg") >>> show(whale_image)
>>> whale_image.shape (428, 1916, 3)
以下函数创建四个变黑区域,以模拟图像上丢失的信息:
def make_mask(image): """Create a mask to artificially defect the image.""" mask = np.zeros(image.shape[:-1]) # Make 4 masks mask[250:300, 1400:1600] = 1 mask[50:100, 300:433] = 1 mask[300:380, 1000:1200] = 1 mask[200:270, 750:950] = 1 return mask.astype(bool) # Create the mask mask = make_mask(whale_image) # Apply the defect mask on the whale_image image_defect = whale_image * ~mask[..., np.newaxis] compare(whale_image, image_defect, "Artifically damaged image of a whale")
我们将使用inpaint模块中的inpaint_biharmonic函数来填充空白,并传递我们创建的掩码:
from skimage.restoration import inpaint restored_image = inpaint.inpaint_biharmonic( image=image_defect, mask=mask, multichannel=True ) compare( image_defect, restored_image, "Restored image after defects", title_original="Faulty Image", )
正如你所看到的,在看到故障图像之前,很难判断缺陷区域在哪里。
现在,让我们制造一些噪声
点击 图像识别资料 拿走腾讯文档-在线文档https://docs.qq.com/doc/DT2dRWmNNRGtmb2pS
2、噪声
如前所述,噪声在图像增强和恢复中起着至关重要的作用。
有时,你可能会有意将其添加到如下图像中:
from skimage.util import random_noise pup = imread("images/pup.jpg") noisy_pup = random_noise(pup) compare(pup, noisy_pup, "Noise puppy image")
我们使用random_noise函数向图像喷洒随机的颜色斑点。因此,这种方法被称为“盐和胡椒(salt和 pepper)”技术。
3、降噪-去噪
但是,大多数情况下,你希望从图像中移除噪声,而不是添加噪声。有几种类型的去噪算法:
TV滤波器
双边去噪
小波降噪
非局部均值去噪
在本文中,我们将只看前两个。我们先试试TV滤波器
from skimage.restoration import denoise_tv_chambolle denoised_pup_tv = denoise_tv_chambolle(noisy_pup, weight=0.2, multichannel=True) compare( noisy_pup, denoised_pup_tv, "Total Variation Filter denoising applied", title_original="Noisy pup", )
图像的分辨率越高,去噪所需的时间就越长。可以使用权重参数控制去噪效果。
现在,让我们尝试denoise_bilateral:
from skimage.restoration import denoise_bilateral denoised_pup_bilateral = denoise_bilateral(noisy_pup, multichannel=True) compare(noisy_pup, denoised_pup_bilateral, "Bilateral denoising applied image")
它不如TV滤波器有效,如下所示:
compare( denoised_pup_tv, denoised_pup_bilateral, "Bilateral filtering", title_original="TV filtering", )
4、分割
图像分割是图像处理中最基本和最日常的主题之一,它广泛应用于运动和目标检测、图像分类等许多领域。
我们已经看到了分割的一个实例—对图像进行阈值化以从前景中提取背景。
本节将学习更多内容,例如将图像分割为类似区域。
要开始分割,我们需要了解超级像素的概念。
一个像素本身只代表一小部分颜色,一旦与图像分离,单个像素将毫无用处。因此,分割算法使用对比度、颜色或亮度相似的多组像素,它们被称为超级像素。
一种试图找到超像素的算法是简单线性迭代聚类(SLIC),它使用k均值聚类。让我们看看如何在skimage库中提供的咖啡图像上使用它:
from skimage import data coffee = data.coffee() >>> show(coffee)
我们将使用segmentation模块中的slic函数:
from skimage.segmentation import slic segments = slic(coffee) >>> show(segments)
默认情况下,slic会查找100个线段或标签。要将它们放回图像中,我们使用label2rgb函数:
from skimage.color import label2rgb final_image = label2rgb(segments, coffee, kind="avg") >>> show(final_image)
让我们将此操作包装在函数中,并尝试使用更多段:
from skimage.color import label2rgb from skimage.segmentation import slic def segment(image, n_segments=100): # Obtain superpixels / segments superpixels = slic(coffee, n_segments=n_segments) # Put the groups on top of the original image segmented_image = label2rgb(superpixels, image, kind="avg") return segmented_image # Find 500 segments coffee_segmented_2 = segment(coffee, n_segments=500) compare(coffee, coffee_segmented_2, "With 500 segments")
分割将使计算机视觉算法更容易从图像中提取有用的特征。
5、等高线
对象的大部分信息都存在于其形状中。如果我们能够检测出物体的线条或轮廓形状,我们就可以提取出有价值的数据。
让我们看看如何在实践中使用多米诺骨牌图像来寻找轮廓。
dominoes = imread("images/dominoes.jpg") >>> show(dominoes)
我们将看看是否可以使用skimage中的find_contours函数来隔离瓷砖和圆。此函数需要一个二进制(黑白)图像,因此我们必须先对图像设置阈值。
from skimage.measure import find_contours # Convert to grayscale dominoes_gray = rgb2gray(dominoes) # Find optimal threshold with treshold_otsu thresh = threshold_otsu(dominoes_gray) # Binarize dominoes_binary = dominoes_gray > thresh domino_contours = find_contours(dominoes_binary)
生成的数组是(n,2)个数组的列表,表示等高线的坐标:
for contour in domino_contours[:5]: print(contour.shape) [OUT]: (371, 2) (376, 2) (4226, 2) (177, 2) (11, 2)
我们将把操作包装在一个名为mark_contours的函数中:
from skimage.filters import threshold_otsu from skimage.measure import find_contours def mark_contours(image): """A function to find contours from an image""" gray_image = rgb2gray(image) # Find optimal threshold thresh = threshold_otsu(gray_image) # Mask binary_image = gray_image > thresh contours = find_contours(binary_image) return contours
要在图像上绘制等高线,我们将创建另一个名为plot_image_contours的函数,该函数使用上述函数:
def plot_image_contours(image): fig, ax = plt.subplots() ax.imshow(image, cmap=plt.cm.gray) for contour in mark_contours(image): ax.plot(contour[:, 1], contour[:, 0], linewidth=2, color="red") ax.axis("off") >>> plot_image_contours(dominoes)
正如我们所看到的,我们成功地检测到了大部分轮廓,但我们仍然可以看到中心的一些随机波动。
在将多米诺骨牌图像传递到轮廓查找函数之前,先进行去噪:
dominoes_denoised = denoise_tv_chambolle(dominoes, multichannel=True) plot_image_contours(dominoes_denoised)
就是这样!我们消除了大部分噪声,这些噪声导致轮廓线不正确!
1、边缘检测
之前,我们使用Sobel算法来检测对象的边缘。在这里,我们将使用Canny算法,因为它更快、更准确,所以得到了更广泛的应用。一如既往,函数canny需要灰度图像。
这一次,我们将使用具有更多硬币的图像,因此需要检测更多边缘:
coins_3 = imread("images/coins_3.jpg") # Convert to gray coins_3_gray = rgb2gray(coins_3) compare(coins_3, coins_3_gray)
要找到边缘,我们只需将图像传递给canny函数:
from skimage.feature import canny # Find edges with canny canny_edges = canny(coins_3_gray) compare(coins_3, canny_edges, "Edges detected with Canny algorithm")
该算法发现了几乎所有硬币的边缘,但由于硬币上的雕刻也被检测到,因此噪声非常大。我们可以通过调整sigma参数来降低canny的灵敏度:
canny_edges_sigma_2 = canny(coins_3_gray, sigma=2.5) compare(coins_3, canny_edges_sigma_2, "Edges detected with less intensity")
正如你所见,Canny现在只找到了硬币的大致轮廓。
2、角点检测
另一种重要的图像处理技术是角点检测。角点可以是图像分类中对象的关键特征。
为了找到角点,我们将使用Harris角点检测算法。让我们加载一个示例图像并将其转换为灰度:
windows = imread("images/windows.jpg") windows_gray = rgb2gray(windows) compare(windows, windows_gray)
我们将使用corner_harris函数生成一个测量图像,该图像屏蔽了角点所在的区域。
from skimage.feature import corner_harris measured_image = corner_harris(windows_gray) >>> show(measured_image)
现在,我们将此蒙版度量图像传递给corner_peaks函数,该函数这次返回角点坐标:
from skimage.feature import corner_peaks corner_coords = corner_peaks(measured_image, min_distance=50) >>> len(corner_coords) 79
该函数找到79个角点。让我们将操作包装到函数中:
def find_corner_coords(image, min_distance=50): # Convert to gray gray_image = rgb2gray(image) # Produce a measure image measure_image = corner_harris(gray_image) # Find coords coords = corner_peaks(measure_image, min_distance=min_distance) return coords
现在,我们将创建另一个函数,该函数使用上述函数生成的坐标绘制每个角点:
def show_image_cornered(image): # Find coords coords = find_corner_coords(image) # Plot them on top of the image plt.imshow(image, cmap="gray") plt.plot(coords[:, 1], coords[:, 0], "+b", markersize=15) plt.axis("off") show_image_cornered(windows)
不幸的是,该算法没有按预期工作。标记放置在砖的交叉处,而不是找到窗角。
这些是噪音,使它们毫无用处。让我们对图像进行去噪处理,并再次将其传递给函数:
windows_denoised = denoise_tv_chambolle(windows, multichannel=True, weight=0.3) show_image_cornered(windows_denoised)
现在,这好多了!它找到了大部分窗户角。学习资料 人工智能《OpenCV图像识别 》领取腾讯文档-在线文档https://docs.qq.com/doc/DT2dRWmNNRGtmb2pS
在真正的计算机视觉问题中,你不会同时使用所有这些。正如你可能已经注意到的,我们今天学到的东西并不复杂,最多需要几行代码。棘手的部分是将它们应用于实际问题,并实际提高模型的性能。