国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

使用Anomalib項目的padim無監(jiān)督算法 進(jìn)行自制工業(yè)缺陷數(shù)據(jù)集的模型訓(xùn)練和ONNX部署(二)——Python代碼解讀篇

這篇具有很好參考價值的文章主要介紹了使用Anomalib項目的padim無監(jiān)督算法 進(jìn)行自制工業(yè)缺陷數(shù)據(jù)集的模型訓(xùn)練和ONNX部署(二)——Python代碼解讀篇。希望對大家有所幫助。如果存在錯誤或未考慮完全的地方,請大家不吝賜教,您也可以點擊"舉報違法"按鈕提交疑問。

目錄

前言

一、padim算法onnx模型輸入輸出解讀

二、padim算法Python代碼處理流程分析

2.1 預(yù)處理部分

2.2?預(yù)測部分

2.3?后處理部分

2.4?可視化部分

三、總結(jié)與展望

前言

? ? ? ? 上一篇博客中完成了Anomalib中padim算法的模型訓(xùn)練,得到了onnx模型以及推理的效果,想看這部分的同學(xué)可以上翻...對于像我一樣根本沒讀論文的同學(xué),獲得了onnx模型以后大概率一臉懵,輸入是什么?輸出是什么?需要經(jīng)過什么樣的預(yù)處理和后處理?如何畫出和Anomalib項目中一樣好看的概率熱圖呢?C++中如何部署?本篇博客會帶大家逐個分析這些問題。本來想和C++部署一起寫的,但是實在太長了。想直接看C++代碼的同學(xué)略過本篇(不過還是建議看一下),下一篇三天內(nèi)發(fā)出來Orz...

一、padim算法onnx模型輸入輸出解讀

? ? ? ? 當(dāng)我們不知道模型里面的結(jié)構(gòu)時,借助Netron工具往往是比較好的辦法,Netron網(wǎng)站地址:

Netronhttps://netron.app/? ? ? ? 將模型拖入,界面上就會顯示網(wǎng)絡(luò)結(jié)構(gòu),以下是padim的onnx模型的輸入和輸出部分的結(jié)構(gòu):

使用Anomalib項目的padim無監(jiān)督算法 進(jìn)行自制工業(yè)缺陷數(shù)據(jù)集的模型訓(xùn)練和ONNX部署(二)——Python代碼解讀篇,python,視覺檢測,人工智能,深度學(xué)習(xí)使用Anomalib項目的padim無監(jiān)督算法 進(jìn)行自制工業(yè)缺陷數(shù)據(jù)集的模型訓(xùn)練和ONNX部署(二)——Python代碼解讀篇,python,視覺檢測,人工智能,深度學(xué)習(xí)

? ? ? ? 可以看出,其輸入尺寸是1*3*256*256,熟悉深度學(xué)習(xí)的同學(xué)應(yīng)該知道這是一個張量(Tensor),它來源于一張3通道RGB圖像,其長寬均為256像素。

????????結(jié)論1:輸入是預(yù)處理后的256*256的RGB圖像。

? ? ? ? 輸出也是一個張量,只不過其尺寸為1*1*256*256,見到和原圖長寬尺寸相等的數(shù)據(jù),我們可以大膽猜測:輸出就是我們想要的概率熱圖,或者某種每個像素位置的得分圖,只不過不一定是最終形式。

? ? ? ? 結(jié)論2:輸出是某種得分圖,經(jīng)過后處理后也許可以得到概率熱圖。

? ? ? ? 總的來說,這個模型的輸入和輸出并不復(fù)雜,這對我們進(jìn)行算法部署是一件好事。

二、padim算法Python代碼處理流程分析

? ? ? ? 我們雖然急于脫離開Anomalib這個復(fù)雜的項目環(huán)境,投入下一步的C++部署。但在此之前,我們必須把Python代碼運行全過程搞明白,才有可能完成C++的改寫。這個過程包括預(yù)處理、推理、后處理和可視化四部分。

? ? ? ? 由于使用了onnx模型進(jìn)行推理,根據(jù)官網(wǎng)教程,此處應(yīng)該使用tools/inference/openvino_inference.py進(jìn)行推理。打開該py文件,很容易在infer函數(shù)中找到以下代碼段:

    for filename in filenames:
        image = read_image(filename, (256, 256))
        predictions = inferencer.predict(image=image)
        output = visualizer.visualize_image(predictions)

? ? ? ? 這幾行代碼就是我們探究推理過程的根源。首先使用read_image讀入圖片,然后調(diào)用inferencer的predict方法得到推理結(jié)果,最后使用visualizer將推理結(jié)果可視化。以上就是使用C++部署時要還原的過程。

? ? ? ? read_image比較簡單,大家可以自行閱讀源碼。這里從inferencer開始看起。這里的inferencer由OpenVINOInferencer實例化而來,閱讀其predict方法,可以找到如下代碼:

        processed_image = self.pre_process(image_arr)              # 預(yù)處理
        predictions = self.forward(processed_image)                # 預(yù)測   
        output = self.post_process(predictions, metadata=metadata) # 后處理

        return ImageResult(
            image=image_arr,
            pred_score=output["pred_score"],
            pred_label=output["pred_label"],
            anomaly_map=output["anomaly_map"],
            pred_mask=output["pred_mask"],
            pred_boxes=output["pred_boxes"],
            box_labels=output["box_labels"],                       # 返回的各項參數(shù)

? ? ? ? 可以看到,圖像經(jīng)過了預(yù)處理、預(yù)測和后處理之后得到了output結(jié)果(字典),返回值為各項參數(shù)。根據(jù)返回值各項參數(shù)的名稱,我們可以知道該函數(shù)返回了預(yù)測的得分、標(biāo)簽類別、異常圖等等。

2.1 預(yù)處理部分

? ? ? ? 首先看pre_process部分,在openvino_inferencer.py中定義了該方法:

    def pre_process(self, image: np.ndarray) -> np.ndarray:
        """Pre process the input image by applying transformations.

        Args:
            image (np.ndarray): Input image.

        Returns:
            np.ndarray: pre-processed image.
        """
        transform = A.from_dict(self.metadata["transform"])
        processed_image = transform(image=image)["image"]

        if len(processed_image.shape) == 3:
            processed_image = np.expand_dims(processed_image, axis=0)

        if processed_image.shape[-1] == 3:
            processed_image = processed_image.transpose(0, 3, 1, 2)

        return processed_image

? ? ? ? 該方法的核心在于transform,通過查看metadata部分代碼,可以認(rèn)為圖片經(jīng)過了與ImageNet相同的標(biāo)準(zhǔn)化預(yù)處理,即RGB的均值為[0.406, 0.456, 0.485],方差為[0.225, 0.224, 0.229]。標(biāo)準(zhǔn)化后需要將其尺寸按照Python代碼所示進(jìn)行修改。

????????結(jié)論3:預(yù)處理步驟包括按照ImageNet標(biāo)準(zhǔn)化處理圖像,和處理圖像的尺寸。

2.2?預(yù)測部分

? ? ? ? 其次看forward部分,就在pre_process部分下方:

    def forward(self, image: np.ndarray) -> np.ndarray:
        """Forward-Pass input tensor to the model.

        Args:
            image (np.ndarray): Input tensor.

        Returns:
            np.ndarray: Output predictions.
        """
        return self.network.infer(inputs={self.input_blob: image})

? ? ? ? 這部分就是將經(jīng)過了pre_process的圖像送入模型進(jìn)行預(yù)測,很好理解。上篇博客說過此處不糾結(jié)神經(jīng)網(wǎng)絡(luò)內(nèi)部的原理,只需要將其當(dāng)作黑盒使用即可。經(jīng)過調(diào)試查看,發(fā)現(xiàn)其輸出確實為與原圖尺寸相等的得分圖,代表了每個像素位置的分?jǐn)?shù),分?jǐn)?shù)越高越有可能是異常區(qū)域。

2.3?后處理部分

? ? ? ? 然后是后處理部分,這部分是四部分中最復(fù)雜的。

    def post_process(self, predictions: np.ndarray, metadata: dict | DictConfig | None = None) -> dict[str, Any]:
        """Post process the output predictions.

        Args:
            predictions (np.ndarray): Raw output predicted by the model.
            metadata (Dict, optional): Meta data. Post-processing step sometimes requires
                additional meta data such as image shape. This variable comprises such info.
                Defaults to None.

        Returns:
            dict[str, Any]: Post processed prediction results.
        """
        if metadata is None:
            metadata = self.metadata

        predictions = predictions[self.output_blob]

        # Initialize the result variables.
        anomaly_map: np.ndarray | None = None
        pred_label: float | None = None
        pred_mask: float | None = None

        # If predictions returns a single value, this means that the task is
        # classification, and the value is the classification prediction score.
        if len(predictions.shape) == 1:
            task = TaskType.CLASSIFICATION
            pred_score = predictions
        else:
            task = TaskType.SEGMENTATION
            anomaly_map = predictions.squeeze()
            pred_score = anomaly_map.reshape(-1).max()

        # Common practice in anomaly detection is to assign anomalous
        # label to the prediction if the prediction score is greater
        # than the image threshold.
        if "image_threshold" in metadata:
            pred_label = pred_score >= metadata["image_threshold"]

        if task == TaskType.CLASSIFICATION:
            _, pred_score = self._normalize(pred_scores=pred_score, metadata=metadata)
        elif task in (TaskType.SEGMENTATION, TaskType.DETECTION):
            if "pixel_threshold" in metadata:
                pred_mask = (anomaly_map >= metadata["pixel_threshold"]).astype(np.uint8)

            anomaly_map, pred_score = self._normalize(
                pred_scores=pred_score, anomaly_maps=anomaly_map, metadata=metadata
            )
            assert anomaly_map is not None

            if "image_shape" in metadata and anomaly_map.shape != metadata["image_shape"]:
                image_height = metadata["image_shape"][0]
                image_width = metadata["image_shape"][1]
                anomaly_map = cv2.resize(anomaly_map, (image_width, image_height))

                if pred_mask is not None:
                    pred_mask = cv2.resize(pred_mask, (image_width, image_height))
        else:
            raise ValueError(f"Unknown task type: {task}")

        if self.task == TaskType.DETECTION:
            pred_boxes = self._get_boxes(pred_mask)
            box_labels = np.ones(pred_boxes.shape[0])
        else:
            pred_boxes = None
            box_labels = None

        return {
            "anomaly_map": anomaly_map,
            "pred_label": pred_label,
            "pred_score": pred_score,
            "pred_mask": pred_mask,
            "pred_boxes": pred_boxes,
            "box_labels": box_labels,
        }

? ? ? ? 上篇博客提到,由于我們使用自制數(shù)據(jù)集,所以task為classification,所以一切TaskType為SEGMETATION和DETECTION的代碼段都不用管。本部分的代碼可以精簡很多:

    def post_process(self, predictions: np.ndarray, metadata: dict | DictConfig | None = None) -> dict[str, Any]:
        """Post process the output predictions.

        Args:
            predictions (np.ndarray): Raw output predicted by the model.
            metadata (Dict, optional): Meta data. Post-processing step sometimes requires
                additional meta data such as image shape. This variable comprises such info.
                Defaults to None.

        Returns:
            dict[str, Any]: Post processed prediction results.
        """
        if metadata is None:
            metadata = self.metadata

        predictions = predictions[self.output_blob]

        # Initialize the result variables.
        anomaly_map: np.ndarray | None = None
        pred_label: float | None = None
        pred_mask: float | None = None

        # If predictions returns a single value, this means that the task is
        # classification, and the value is the classification prediction score.
        if len(predictions.shape) == 1:
            task = TaskType.CLASSIFICATION
            pred_score = predictions

        # Common practice in anomaly detection is to assign anomalous
        # label to the prediction if the prediction score is greater
        # than the image threshold.
        if "image_threshold" in metadata:
            pred_label = pred_score >= metadata["image_threshold"]

        if task == TaskType.CLASSIFICATION:
            _, pred_score = self._normalize(pred_scores=pred_score, metadata=metadata)
        
        pred_boxes = None
        box_labels = None

        return {
            "anomaly_map": anomaly_map,
            "pred_label": pred_label,
            "pred_score": pred_score,
            "pred_mask": pred_mask,
            "pred_boxes": pred_boxes,
            "box_labels": box_labels,
        }

? ? ? ? 閱讀源碼,發(fā)現(xiàn)本部分有意義的輸出只有pred_score,真正的處理步驟只有一行:

_, pred_score = self._normalize(pred_scores=pred_score, metadata=metadata)

? ? ? ? 進(jìn)入_normalize部分,可以看到輸入的pred_scores是一個張量。事實上,pred_scores即為和原圖尺寸相等的概率得分圖。同樣,輸入的pred_scores也只處理了一步,即:

            pred_scores = normalize_min_max(
                pred_scores,
                metadata["image_threshold"],
                metadata["min"],
                metadata["max"],
            )

? ? ? ? 再進(jìn)入normalize_min_max部分,可以看到該函數(shù)對pred_scores進(jìn)行了如下處理:

normalized = ((targets - threshold) / (max_val - min_val)) + 0.5

? ? ? ? 這里的max_val和min_val來源于哪里呢?打開訓(xùn)練結(jié)果文件夾results/padim/tube/run/weights/onnx/metadata.json,可以文件末尾看到如下信息(tube是我自己的數(shù)據(jù)集名字):

    "image_threshold": 13.702226638793945,
    "pixel_threshold": 13.702226638793945,
    "min": 5.296699047088623,
    "max": 22.767864227294922

? ? ? ? min即為min_val,max即為max_val,其含義分別為pred_score得分圖中的最小值和最大值,image_threshold是計算得到的閾值,得分圖中大于該閾值的像素位置我們認(rèn)為它屬于異常區(qū)域(缺陷),小于該閾值的區(qū)域認(rèn)為是正常區(qū)域。經(jīng)過這一步標(biāo)準(zhǔn)化以后輸出pred_scores。

? ? ? ? 結(jié)論4:后處理部分輸入為預(yù)測部分得到的得分圖,輸出為標(biāo)準(zhǔn)化后的pred_scores。

2.4?可視化部分

? ? ? ? 至此,數(shù)據(jù)處理部分結(jié)束,接下來就是如何將數(shù)據(jù)以概率熱圖的方式可視化的問題了?;氐給penvino_inference.py,可以看到visualizer調(diào)用了visualize_image方法對數(shù)據(jù)結(jié)果predictions進(jìn)行了處理,并使用show方法進(jìn)行可視化。

    for filename in filenames:
        image = read_image(filename, (256, 256))
        predictions = inferencer.predict(image=image)
        output = visualizer.visualize_image(predictions)

        if args.output is None and args.show is False:
            warnings.warn(
                "Neither output path is provided nor show flag is set. Inferencer will run but return nothing."
            )

        if args.output:
            file_path = generate_output_image_filename(input_path=filename, output_path=args.output)
            visualizer.save(file_path=file_path, image=output)

        # Show the image in case the flag is set by the user.
        if args.show:
            visualizer.show(title="Output Image", image=output)

?????????進(jìn)入visualize_image方法,我們之前在config.yaml文件中將顯示模式設(shè)為full,visualize_image方法內(nèi)使用的是_visualize_full方法。

        if self.mode == "full":
            return self._visualize_full(image_result)

? ? ? ? 層層遞進(jìn),進(jìn)入_visualize_full方法,同上,只需要關(guān)注task為CLASSIFICATION的代碼段。在_visualize_full方法中,可以看到如下代碼:

        elif self.task == TaskType.CLASSIFICATION:
            visualization.add_image(image_result.image, title="Image")
            if hasattr(image_result, "heat_map"):
                visualization.add_image(image_result.heat_map, "Predicted Heat Map")
            if image_result.pred_label:
                image_classified = add_anomalous_label(image_result.image, image_result.pred_score)
            else:
                image_classified = add_normal_label(image_result.image, 1 - image_result.pred_score)
            visualization.add_image(image=image_classified, title="Prediction")

? ? ? ? visualization.add_image的作用實際上就是往results/padim/tube/run/images的結(jié)果中添加圖片,添加的三張圖片分別為“Image”“Predicted Heat Map”“Prediction”,恰好對應(yīng)輸出的1*3結(jié)果圖:

使用Anomalib項目的padim無監(jiān)督算法 進(jìn)行自制工業(yè)缺陷數(shù)據(jù)集的模型訓(xùn)練和ONNX部署(二)——Python代碼解讀篇,python,視覺檢測,人工智能,深度學(xué)習(xí)

? ? ? ? ?這里最關(guān)注的還是Predicted Heat Map是怎么畫出來的。進(jìn)入image_result.heat_map,發(fā)現(xiàn)它是調(diào)用了superimpose_anomaly_map函數(shù)生成的:

self.heat_map = superimpose_anomaly_map(self.anomaly_map, self.image, normalize=False)

?????????再次進(jìn)入superimpose_anomaly_map函數(shù),其代碼段如下:

def superimpose_anomaly_map(
    anomaly_map: np.ndarray, image: np.ndarray, alpha: float = 0.4, gamma: int = 0, normalize: bool = False
) -> np.ndarray:
    """Superimpose anomaly map on top of in the input image.

    Args:
        anomaly_map (np.ndarray): Anomaly map
        image (np.ndarray): Input image
        alpha (float, optional): Weight to overlay anomaly map
            on the input image. Defaults to 0.4.
        gamma (int, optional): Value to add to the blended image
            to smooth the processing. Defaults to 0. Overall,
            the formula to compute the blended image is
            I' = (alpha*I1 + (1-alpha)*I2) + gamma
        normalize: whether or not the anomaly maps should
            be normalized to image min-max


    Returns:
        np.ndarray: Image with anomaly map superimposed on top of it.
    """

    anomaly_map = anomaly_map_to_color_map(anomaly_map.squeeze(), normalize=normalize)
    superimposed_map = cv2.addWeighted(anomaly_map, alpha, image, (1 - alpha), gamma)
    return superimposed_map

????????這里的英文注釋寫的很好了,其實概率熱圖就是將未處理的原圖像和處理后的anomaly_map進(jìn)行了一定的權(quán)重疊加,繪制出的圖像。疊加之前,需要對輸入的anomaly_map進(jìn)行anomaly_map_to_color_map函數(shù)的處理,而anomaly_map_to_color_map就是將anomaly_map轉(zhuǎn)化為uint8格式的灰度圖(像素值0-255),然后根據(jù)灰度值繪制偽彩色圖:

anomaly_map = cv2.applyColorMap(anomaly_map, cv2.COLORMAP_JET)

????????疊加處理后,一幅清晰明艷的缺陷概率熱圖就產(chǎn)生了。

? ? ? ? 結(jié)論5:可視化過程中,概率熱圖為原圖和偽彩色anomaly_map的疊加圖像。

? ? ? ? 到這里,我們已經(jīng)在Python代碼的解讀中拉通了整個流程:從輸入圖像到預(yù)處理、預(yù)測、后處理和可視化,了解了概率熱圖的繪制方法。

三、總結(jié)與展望

? ? ? ? 若想在C++中部署模型,本篇博客這樣耗時又繁瑣的代碼閱讀過程是少不了的。只有先明白原工程中Python代碼的邏輯,剝離完整的流程,才可能在C++中進(jìn)行復(fù)現(xiàn)。下一篇博客將以C++代碼為主,講解如何使用OnnxRuntime引擎完成模型的部署。感謝閱讀和關(guān)注~文章來源地址http://www.zghlxwxcb.cn/news/detail-737535.html

到了這里,關(guān)于使用Anomalib項目的padim無監(jiān)督算法 進(jìn)行自制工業(yè)缺陷數(shù)據(jù)集的模型訓(xùn)練和ONNX部署(二)——Python代碼解讀篇的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實不符,請點擊違法舉報進(jìn)行投訴反饋,一經(jīng)查實,立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費用

相關(guān)文章

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包