介绍
在本文中,我们将使用 CNN 和机器学习分类器创建一个 Mask v/s No Mask 分类器。它将检测一个人是否戴着口罩。我们将从头开始学习一切,我将解释每一步。我需要你对机器学习和数据科学有基本的了解。我已经在我的本地 Windows 10 机器上实现了它,但是如果你愿意,你也可以在 Google Colab 上实现它。
卷积神经网络 (CNN) 是一种人工神经网络,旨在处理像素数据。它们在图像处理和图像识别中明确使用。
![](https://editor.analyticsvidhya.com/uploads/227001.png)
CNN 模型管道
首先,我们将输入大小为 224×224 像素的 RGB 图像。然后这些图像将进入一个 CNN 模型,从中提取 128 个相关的特征向量。然后我们将使用这些特征向量来训练我们的各种机器学习分类器,如逻辑回归、随机森林等,以分类该图像中的人是否戴着口罩。您可以参考下图以获得更好的理解。
![美国有线电视新闻网](https://editor.analyticsvidhya.com/uploads/733302.png)
导入必要的库:
我们将导入此项目所需的所有必要库。
我们将使用像 Numpy 这样的库,用于执行复杂的数学计算。Pandas 加载和预处理数据集,并使用了更多库。
将 numpy 导入为 np 将熊猫导入为 pd 将 matplotlib.pyplot 导入为 plt 导入操作系统 从 itertools 导入周期 从 sklearn.model_selection 导入 train_test_split 从 tensorflow.keras.models 导入模型 从 tensorflow.keras.layers 导入 Dropout, Dense, AveragePooling2D, Flatten ,Dense, Input 从 sklearn.metrics 导入分类报告,混淆矩阵 导入简历2
从 sklearn.metrics 导入 roc_curve,auc 从 sklearn.preprocessing 导入 label_binarize 从 scipy 导入 interp 从 sklearn.ensemble 导入 RandomForestClassifier 从 tensorflow.keras.preprocessing.image 导入 ImageDataGenerator 从 tensorflow.keras.applications 导入 MobileNetV2 从 tensorflow.keras.optimizers 导入 Adam
数据集的加载和预处理
您可以从该GitHub Repo下载数据集。
从上述存储库中克隆您的数据集。
该数据集包含 1200 多张不同人是否戴口罩的图像。加载数据集后,我们将对它进行预处理。它涉及拆分成训练和测试数据集,将像素值转换为 0 到 1 之间,并将标签转换为 one-hot 编码标签。
下面是加载和预处理数据集的代码。它的注释很好,因此您可以轻松理解它。
![美国有线电视新闻网](https://editor.analyticsvidhya.com/uploads/782456.png)
在下面的代码中,我们将首先从文件夹中读取所有图像,然后通过将它们调整为 224×224 像素将它们存储在一个数组中。之后,我们将标记这些图像。带有掩码的图像有一个标签 0,没有掩码的图像有一个标签 1。最后,我们将使用名为 train test split 的 sklearn 函数将此数据集拆分为训练和测试。
# 列出主目录中所有带有掩码的图像。
文件名 = os.listdir("observations-master/experiments/data/with_mask") np.random.shuffle(文件名) print(filenames) # 从该目录中读取所有图像并将它们调整为 224x224 像素。 with_mask_data = [cv2.resize(cv2.imread("observations-master/exeriements/data/with_mask/"+img), (224,224)) 用于文件名中的 img] 打印(len(with_mask_data))
# 对于不包含掩码的图像,执行上述类似步骤。
文件名 = os.listdir("observations-master/experiments/data/without_mask") np.random.shuffle(文件名) 打印(文件名) without_mask_data = [cv2.resize(cv2.imread("observations-master/exeriements/data/without_mask/"+img), (224,224)) 用于文件名中的 img] 打印(长度(无掩码数据))
# 我们将两个数组组合成一个数组,通过将它们除以 255 将每个像素值在 0 和 1 之间转换。
data = np.array(with_mask_data + without_mask_data).astype('float32')/255 # 带掩码图像的标签 - 0 # 不带掩码的图像标签 - 1 标签 = np.array([0]*len(with_mask_data) + [1]*len(without_mask_data)) print(data.shape) # 将数据拆分为训练集和测试集。 (training_data,testing_data,training_label,testing_label)=train_test_split(数据,标签,test_size=0.50,stratify=labels,random_state=42)
print(training_data.shape) # 绘制准确度/损失曲线的函数 def plot_acc_loss(结果,时期): acc = result.history['准确性'] loss = result.history['loss'] val_acc = result.history['val_accuracy'] val_loss = result.history['val_loss'] plt.figure(figsize=(15, 5)) plt.subplot(121) plt.plot(range(1,epochs), acc[1:], label='Train_acc') plt.plot(范围(1,epochs), val_acc[1:], label='Val_acc') plt.title('精度超过 ' + str(epochs) + ' Epochs', size=15) plt.legend() plt.grid(真) plt.subplot(122) plt.plot(range(1,epochs), loss[1:], label='Train_loss') plt.plot(range(1,epochs), val_loss[1:], label='Val_loss') plt.title('损失超过' + str(epochs) + 'Epochs', size=15) plt.legend() plt.grid(真) plt.show()
构建卷积神经网络 (CNN)
现在我们将构建我们的卷积神经网络。首先,我们使用图像数据生成器来增加数据集中的图像数量。该图像生成器将从这些现有图像中生成更多照片。它执行顺时针或逆时针旋转,改变对比度,执行放大或缩小等。
之后,我们将使用预训练的 MobileNetV2 架构来训练我们的模型。它是一种迁移学习模型。迁移学习是使用预训练模型来训练新的深度学习模型,即如果两个模型执行相似的任务,我们可以共享知识。应用迁移学习后,我们将应用展平层将 2D 矩阵转换为 1D 数组。之后,我们将应用密集层和丢失层来执行分类。
最后,我们将批量大小设为 32,时期数设为 25 来训练我们的模型。您可以根据自己的计算能力取任何其他值。
![美国有线电视新闻网](https://editor.analyticsvidhya.com/uploads/399613.png)
训练卷积神经网络模型的代码:
我们将构建我们的迁移学习MobileNetV2 架构,这是一个预训练的 CNN 模型。首先,我们将使用图像数据生成器从我们的数据集中生成更多图像。之后,我们将设置我们的超参数,如学习率、批量大小、编号。时期等。最后,我们将训练我们的模型并在测试集上检查它的准确性。
# 图像数据生成器以生成更多图像。 生成器 = ImageDataGenerator( 旋转范围=20, 缩放范围=0.15, width_shift_range=0.2, height_shift_range=0.2, 剪切范围=0.15, 水平翻转=真, 填充模式=“最近”)
# 设置超参数。
学习率 = 0.0001 纪元 = 25 批量大小 = 32 # 训练移动网络 v2 架构。
transfer_learning_model = MobileNetV2(weights="imagenet", include_top=False, 输入张量=输入(形状=(224、224、3))) model_main = transfer_learning_model.output model_main = AveragePooling2D(pool_size=(7, 7))(model_main) # 应用扁平化层。 模型主 = 展平(名称 =“展平”)(模型主) model_main = Dense(128, activation="relu", name="dense_layer")(model_main) model_main = Dropout(0.5)(model_main) model_main = Dense(2, activation="softmax")(model_main) cnn = 模型(输入=transfer_learning_model.input,输出=model_main) 对于 transfer_learning_model.layers 中的行: row.trainable = False 优化器 = Adam(lr=learning_rate, decay=learning_rate / epoch) cnn.compile(loss="sparse_categorical_crossentropy", 优化器=优化器, 指标=[“准确性”]) # 训练CNN模型 历史 = cnn.fit( generator.flow(training_data, training_label, batch_size=batch_size), steps_per_epoch=len(training_data) // batch_size, 验证数据=(测试数据,测试标签), validation_steps=len(testing_data) // batch_size, 时代=时代) 评估训练好的模型
# 在测试集上评估模型。 cnn.evaluate(测试数据,测试标签) plot_acc_loss(历史,25)
![美国有线电视新闻网](https://editor.analyticsvidhya.com/uploads/534264.png)
我们在测试集上获得了 99.42% 的准确率
使用机器学习分类器
现在,我们将从之前训练的 CNN 模型中提取 128 个相关特征向量,并将它们应用于不同的 ML 分类器。我们将使用以下机器学习分类器:
Xtreme Gradient Boosting:
Extreme Gradient Boosting (XGBoost) 是一个开源库,可以高效地实现梯度提升算法。首先,导入必要的库,然后将分类器定义为 XGBClassifier。拟合后,表示预测和准确度得分。我们得到准确率、混淆矩阵和分类报告作为输出。在这里,我们得到了 98.98% 的准确率。
随机森林分类器:
随机森林是一个分类器,它在给定数据集的不同子集上包含多个决策树,并取平均值以提高该数据集的预测准确性。森林中的树木数量越多,精度越高,并且可以防止过度拟合的问题。首先,导入必要的库,然后将分类器定义为 RandomForestClassifier。拟合后,表示预测和准确度得分。我们得到准确率、混淆矩阵和分类报告作为输出。在这里,我们的准确率达到了 99.41%,超过了 XGBoost。
逻辑回归:
逻辑回归是一种监督学习分类算法,用于预测目标变量的概率。目标或因变量的性质是二分法的,这意味着只有两个可能的类别。在这里,我们的准确率达到了 99.70%,高于 XGBoost,但略低于随机森林。
高斯分布:
围绕均值对称的概率分布是正态分布,有时称为高斯分布。它表明接近平均值的数据比远离平均值的数据更频繁地出现。钟形曲线表示图形上的正态分布。
下面是提取基本特征向量并将这些特征向量放入机器学习分类器的代码。
在训练我们的 CNN 模型之后,我们现在将应用特征提取并从这些图像中提取 128 个相关的特征向量。这些适当的特征向量被输入到我们的各种机器学习分类器中以执行最终分类。
我们使用了各种机器学习模型,例如 XGBoost、随机森林、逻辑回归、GaussianNB 等。我们将选择能够提供最佳准确度的模型。
从 keras.models 导入模型 layer_name='dense_layer' # 从上面的 CNN 模型中提取层,其中包含 128 个神经元。 new_model = 模型(输入=cnn.input, 输出=cnn.get_layer(layer_name).output) new_model.summary() # 获取仅包含这 128 个特征的新训练数据。 training_image_features = new_model.predict(training_data) training_image_features = pd.DataFrame(data=training_image_features) testing_image_features = new_model.predict(testing_data) testing_image_features = pd.DataFrame(data=testing_image_features # 使用 XGBoost 分类器进行分类。从 xgboost 导入 XGBClassifier 从 sklearn.metrics 导入 accuracy_score 分类器 = XGBClassifier() 分类器.fit(training_image_features,training_label) predictions = classifier.predict(testing_image_features) # 获取准确率分数 准确度=准确度得分(预测,测试标签) print(f'{accuracy*100}%') # 获取混淆矩阵。 cf = 混淆矩阵(预测,测试标签) 打印(cf) 从 sklearn.metrics 导入分类报告 c_r = 分类报告(预测,测试标签,输出字典=真) 打印(c_r) # 使用 RandomForest 分类器进行分类。 从 sklearn.ensemble 导入 RandomForestClassifier rfc = RandomForestClassifier() rfc.fit(training_image_features,training_label) 预测 = rfc.predict(testing_image_features) 准确度=准确度分数(预测,测试标签) 打印(f'{准确度*100}%') cf = 混淆矩阵(预测,测试标签) 打印(cf) 从 sklearn.metrics 导入分类报告 c_r = 分类报告(预测,测试标签,输出字典=真) 打印(c_r) # 使用 LogisticRegression 进行分类 从 sklearn.linear_model 导入 LogisticRegression lin_r = 逻辑回归() lin_r.fit(training_image_features, training_label) 预测 = lin_r.predict(testing_image_features) 准确度=准确度分数(预测,测试标签) 打印(f'{准确度*100}%') cf = 混淆矩阵(预测,测试标签) 打印(cf) 从 sklearn.metrics 导入分类报告 c_r = 分类报告(预测,测试标签,输出字典=真) 打印(c_r) # 使用 GaussianNB 进行分类 从 sklearn.naive_bayes 导入 GaussianNB n_b = GaussianNB() n_b.fit(training_image_features,training_label) 预测 = n_b.predict(testing_image_features) 准确度=准确度分数(预测,测试标签) 打印(f'{准确度*100}%') cf = 混淆矩阵(预测,测试标签) 打印(cf) 从 sklearn.metrics 导入分类报告 c_r = 分类报告(预测,测试标签,输出字典=真) 打印(c_r)
结果
在本节中,我们将讨论我们的分类结果。我们将讨论我们达到了多少准确度,以及准确率、召回率和 f1 分数是多少。
准确性: 评估分类模型的一个参数是准确性。我们的模型正确预测的预测百分比称为准确性。以下是准确度的官方定义:准确猜测的数量等于总体猜测的准确度。
Precision: Precision 的计算方法是用阳性预测的总数除以真阳性的比例(即真阳性的数量加上假阳性的数量)。
F1 分数:机器学习中最重要的评估指标之一是 F1 分数。结合准确率和召回率这两个通常在竞争中的衡量标准,它优雅地总结了模型的预测能力。
以下是我们用来训练模型的所有机器学习分类器的性能得分。逻辑回归给出的准确率最高,为 99.709%。
![](https://cdn.analyticsvidhya.com/wp-content/uploads/2022/09/Screenshot-from-2022-10-10-10-41-22.png)
所有机器学习分类器
的混淆矩阵是: 混淆矩阵是一个总结预测结果的 NxN 矩阵。它包含每个类别破坏的正确和错误预测的数量。
完整代码
在本节中,我分享了该项目中使用的完整代码。除了上述代码之外,此代码还包含绘制机器学习模型的 ROC-AUC 曲线的代码。
ROC 曲线(接收器操作特性曲线)是显示分类模型在所有分类阈值下的性能的图表。该曲线绘制了两个参数:真阳性率。误报率。
首先我们加载数据集。然后我们使用 OpenCV 库读取图像,并将它们转换为 224×224 像素大小,并将它们存储在一个数组中。之后,我们必须为这两个类制作标签,即掩码和无掩码。然后我们讨论了图像数据生成器和 MobileNetV2 架构的代码。此外,我们在设置了 epochs、batch size 等超参数后训练了我们的 CNN 模型。在完成 25 个 epochs 后,我们在测试集上获得了 99.42% 的准确率。
在训练完 CNN 模型后,我们应用特征提取,从密集层中提取了 128 个特征向量,并将这些特征向量应用到机器学习模型中,得到最终的分类。然后我们编写了评估各种性能矩阵的代码,如准确度得分、F1 得分、精度等。最后,我们绘制了性能最佳的机器学习模型的 ROC-AUC 曲线。
将 numpy 导入为 np 将熊猫导入为 pd 将 matplotlib.pyplot 导入为 plt 导入操作系统 从 itertools 导入周期 从 sklearn.model_selection 导入 train_test_split 从 tensorflow.keras.models 导入模型 从 tensorflow.keras.layers 导入 Dropout, Dense, AveragePooling2D, Flatten ,Dense, Input 从 sklearn.metrics 导入分类报告,混淆矩阵 导入简历2 从 sklearn.metrics 导入 roc_curve,auc 从 sklearn.preprocessing 导入 label_binarize 从 scipy 导入 interp 从 sklearn.ensemble 导入 RandomForestClassifier 从 tensorflow.keras.preprocessing.image 导入 ImageDataGenerator 从 tensorflow.keras.applications 导入 MobileNetV2 从 tensorflow.keras.optimizers 导入 Adam def plot_acc_loss(结果,时期): acc = result.history['准确性'] loss = result.history['loss'] val_acc = result.history['val_accuracy'] val_loss = result.history['val_loss'] plt.figure(figsize=(15, 5)) plt.subplot(121) plt.plot(range(1,epochs), acc[1:], label='Train_acc') plt.plot(范围(1,epochs), val_acc[1:], label='Val_acc') plt.title('精度超过 ' + str(epochs) + ' Epochs', size=15) plt.legend() plt.grid(真) plt.subplot(122) plt.plot(range(1,epochs), loss[1:], label='Train_loss') plt.plot(range(1,epochs), val_loss[1:], label='Val_loss') plt.title('损失超过' + str(epochs) + 'Epochs', size=15) plt.legend() plt.grid(真) plt.show() # 文件名 = glob(mypath +'with_mask/'+'*.jpg') 文件名 = os.listdir("observations-master/experiments/data/with_mask") np.random.shuffle(文件名) 打印(文件名)#460,116 with_mask_data = [cv2.resize(cv2.imread("observations-master/exeriements/data/with_mask/"+img), (224,224)) 用于文件名中的 img] 打印(len(with_mask_data)) 文件名 = os.listdir("observations-master/experiments/data/without_mask") np.random.shuffle(文件名) 打印(文件名)#460,116 without_mask_data = [cv2.resize(cv2.imread("observations-master/exeriements/data/without_mask/"+img), (224,224)) 用于文件名中的 img] 打印(长度(无掩码数据)) 数据 = np.array(with_mask_data + without_mask_data).astype('float32')/255 标签 = np.array([0]*len(with_mask_data) + [1]*len(without_mask_data)) 打印(数据。形状) (training_data,testing_data,training_label,testing_label)=train_test_split(数据,标签,test_size=0.50,stratify=labels,random_state=42) 打印(training_data.shape) 生成器 = ImageDataGenerator( 旋转范围=20, 缩放范围=0.15, width_shift_range=0.2, height_shift_range=0.2, 剪切范围=0.15, 水平翻转=真, 填充模式=“最近”) 学习率 = 0.0001 纪元 = 25 批量大小 = 32 transfer_learning_model = MobileNetV2(weights="imagenet", include_top=False, 输入张量=输入(形状=(224、224、3))) model_main = transfer_learning_model.output model_main = AveragePooling2D(pool_size=(7, 7))(model_main) 模型主 = 展平(名称 =“展平”)(模型主) model_main = Dense(128, activation="relu", name="dense_layer")(model_main) model_main = Dropout(0.5)(model_main) model_main = Dense(2, activation="softmax")(model_main) cnn = 模型(输入=transfer_learning_model.input,输出=model_main) 对于 transfer_learning_model.layers 中的行: row.trainable = False 优化器 = Adam(lr=learning_rate, decay=learning_rate / epoch) cnn.compile(loss="sparse_categorical_crossentropy", 优化器=优化器, 指标=[“准确性”]) 历史 = cnn.fit( generator.flow(training_data, training_label, batch_size=batch_size), steps_per_epoch=len(training_data) // batch_size, 验证数据=(测试数据,测试标签), validation_steps=len(testing_data) // batch_size, 时代=时代) cnn.evaluate(测试数据,测试标签) plot_acc_loss(历史,25) 从 keras.models 导入模型 layer_name='dense_layer' new_model = 模型(输入=cnn.input, 输出=cnn.get_layer(layer_name).output) new_model.summary() training_image_features = new_model.predict(training_data) training_image_features = pd.DataFrame(data=training_image_features) testing_image_features = new_model.predict(testing_data) testing_image_features = pd.DataFrame(data=testing_image_features) 从 xgboost 导入 XGBClassifier 从 sklearn.metrics 导入 accuracy_score 分类器 = XGBClassifier() 分类器.fit(training_image_features,training_label) 预测 = 分类器.预测(测试图像特征) 准确度=准确度得分(预测,测试标签) 打印(f'{准确度*100}%') cf = 混淆矩阵(预测,测试标签) 打印(cf) 从 sklearn.metrics 导入分类报告 c_r = 分类报告(预测,测试标签,输出字典=真) 打印(c_r) 从 sklearn.ensemble 导入 RandomForestClassifier rfc = RandomForestClassifier() rfc.fit(training_image_features,training_label) 预测 = rfc.predict(testing_image_features) 准确度=准确度分数(预测,测试标签) 打印(f'{准确度*100}%') cf = 混淆矩阵(预测,测试标签) 打印(cf) 从 sklearn.metrics 导入分类报告 c_r = 分类报告(预测,测试标签,输出字典=真) 打印(c_r) 从 sklearn.linear_model 导入 LogisticRegression lin_r = 逻辑回归() lin_r.fit(training_image_features, training_label) 预测 = lin_r.predict(testing_image_features) 准确度=准确度分数(预测,测试标签) 打印(f'{准确度*100}%') cf = 混淆矩阵(预测,测试标签) 打印(cf) 从 sklearn.metrics 导入分类报告 c_r = 分类报告(预测,测试标签,输出字典=真) 打印(c_r) 从 sklearn.naive_bayes 导入 GaussianNB n_b = GaussianNB() n_b.fit(training_image_features,training_label) 预测 = n_b.predict(testing_image_features) 准确度=准确度分数(预测,测试标签) 打印(f'{准确度*100}%') cf = 混淆矩阵(预测,测试标签) 打印(cf) 从 sklearn.metrics 导入分类报告 c_r = 分类报告(预测,测试标签,输出字典=真) 打印(c_r) # 将输出二值化 y = label_binarize(training_label, classes=[0, 1]) y_test = label_binarize(testing_label, classes=[0, 1]) n_classes = 2 # 学习预测每个类与其他类 分类器 = RandomForestClassifier() 分类器.fit(training_image_features, y) y_score = classifier.predict(testing_image_features) 打印(accuracy_score(y_score,y_test)) # 计算每个类的ROC曲线和ROC面积 fpr = 字典() tpr = 字典() roc_auc = dict() 对于我在范围内(n_classes): fpr[i], tpr[i], _ = roc_curve(y_test[:, i], y_score[:, i]) roc_auc[i] = auc(fpr[i], tpr[i]) # 计算微平均 ROC 曲线和 ROC 面积 fpr["micro"], tpr["micro"], _ = roc_curve(y_test.ravel(), y_score.ravel()) roc_auc["micro"] = auc(fpr["micro"], tpr["micro"]) # 首先汇总所有误报率 all_fpr = np.unique(np.concatenate([fpr[i] for i in range(n_classes)])) # 然后在这些点插入所有ROC曲线 mean_tpr = np.zeros_like(all_fpr) 对于我在范围内(n_classes): mean_tpr += interp(all_fpr, fpr[i], tpr[i]) # 最后,平均它并计算 AUC mean_tpr /= n_classes fpr[“宏”] = all_fpr tpr [“宏”] = mean_tpr roc_auc["macro"] = auc(fpr["macro"], tpr["macro"]) # 绘制所有 ROC 曲线 plt.figure() plt.plot(fpr["micro"], tpr["micro"], label='微平均 ROC 曲线(面积 = {0:0.2f})' ''.format(roc_auc["micro"]), 颜色='deeppink',线型=':',线宽=4) plt.plot(fpr["macro"], tpr["macro"], label='宏观平均 ROC 曲线(面积 = {0:0.2f})' ''.format(roc_auc["macro"]), 颜色='海军',线型=':',线宽=4) 颜色 = 循环(['aqua', 'darkorange', '矢车菊蓝']) 对于 i,zip 中的颜色(范围(n_classes),颜色): plt.plot(fpr[i], tpr[i], 颜色=颜色, label='类别 {0} 的 ROC 曲线(面积 = {1:0.2f})' ''.format(i, roc_auc[i])) plt.plot([0, 1], [0, 1], 'k--') plt.xlim([0.0, 1.0]) plt.ylim([0.0, 1.05]) plt.xlabel('误报率') plt.ylabel('真阳性率') plt.title('Receiver操作特性对多类的一些扩展') plt.legend(loc="右下") plt.show()
使用的实验设置:
我们已经使用 Python 3.8 编程语言和 IntelR Core i5-1155G7 CPU @ 2.30GHz × 8 处理器和 8GB RAM 在 Windows 10 上运行,带有 NVIDIA Geforce MX 350 和 2GB 图形,实现了建议的分类系统。
结论
在这项工作中,我们展示了使用卷积网络和机器学习分类器来有效地对 Mask 和 No Mask 进行分类。我们还在数据集中使用了图像增强来标准化图像。通过 CNN 提取图像特征后,应用机器学习算法进行最终分类,卷积神经网络获得了最佳结果,随机森林的准确率分别为 99.42% 和 99.21%,逻辑回归的准确率为 99.70%,这是所有方法中最高的. 因此,这种图像处理方法和图像处理技术可以成为一种大规模、更快且具有成本效益的分类方法。使用更大规模的数据集进行训练并在更大的队列中进行现场测试可以提高准确性。
本文的主要内容:
1. 我们讨论了 CNN 和机器学习分类器。
2. 然后,我们跳到编码部分,讨论加载和预处理数据集。
3. 此外,我们已经训练了 CNN 模型,然后讨论了测试和验证的准确性。
4. 之后,我们提取特征向量并将它们放入机器学习分类器中。
5. 最后,我们结束了这篇文章。
原文标题:Detecting If a Person is Wearing a Mask or Not Using CNN
原文作者:Aryan Garg
原文链接:analyticsvidhya.com/blog/2022/10/detecting-if-a-person-is-wearing-a-mask-or-not-using-cnn/