一个案例展示opencv在医学图像病灶分割任务的常用操作

任务

根据语义分割网络的输出,去求以下任务的结果。(神经网络的后处理操作)
(1)画出病灶边界
(2)画出贴近的椭圆
(3)过滤病灶内的噪声
(4)计算病灶的不规则周长
(5)计算病灶面积
(6)画出径线

原图

预处理

假设网络为一个二分类网络,网络的输出是一个二维的tensor,每个像素的值为0~1的置信度。先预处理操作,通过设置阈值把每个像素转换为0或者255,分别代表背景和目标,生成mask图像。储存为cv::Mat 8UC1的模式,图片格式为8位单通道,2^8 = 256, 对应每个像素的取值范围为0 ~ 255。

一个案例展示opencv在医学图像病灶分割任务的常用操作

CODE

#include <iostream>
#include <opencv2/opencv.hpp>
using namespace cv;
using namespace std;

// 输入直线的两个端点,计算直线的中点
cv::Point calMidpoint(Point2f p1, Point2f p2){
    Point p;
    p.x = int((p1.x + p2.x) / 2);
    p.y = int((p1.y + p2.y) / 2);
    return p;
}

int main()
{
    try {
    	// 这是上面原图的三通道版本的图像,实际原图应该为单通道(灰度)图
        Mat img = imread("test.jpg");
        // 创建储存检测目标的边界的点的集合(会存在多个边界)
        vector<vector<Point>> cnts;
        vector<Vec4i> hierarchy;
        Mat img2;
        // 过滤病灶内的噪声,但是因为侵蚀操作会影响病灶边界产生变化,Size(10, 10)根据实际情况调整
        Mat kernel = getStructuringElement(MORPH_RECT, Size(10, 10));
        morphologyEx(img, img, MORPH_CLOSE, kernel);
        // 将RGB三通道转单通道(灰度)
        cvtColor(img, img2, CV_RGB2GRAY);
        // 设置阈值将所有像素的值变成0或者255
        threshold(img2, img2, 100, 255, THRESH_BINARY);
        // 寻找病灶的边界
        // 第4和第5个参数可查看这两个枚举类型的说明cv::RetrievalModes和cv::ContourApproximationModes
        findContours(img2, cnts, hierarchy, cv::RetrievalModes::RETR_LIST, cv::ContourApproximationModes::CHAIN_APPROX_SIMPLE);
        
        if (cnts.size() > 0) {
        	// 查找点(Point)最多的一组点集作为边界
            vector<Point> contour;
            int max_index = -1;
            int max_size = 0;
            for (int i = 0; i < cnts.size(); i++) {
                if(cnts[i].size() > max_size){
                    max_size = cnts[i].size();
                    max_index = i;
                }
            }
            // 根据这组点集找到最合适的拟合椭圆(非外接椭圆)
            RotatedRect rect = fitEllipse(cnts[max_index]);
            // 获得这个拟合椭圆的外接矩形的四个角点
            Point2f pts[4];
            // The order is bottomLeft, topLeft, topRight, bottomRight.
            rect.points(pts);
			// 算出椭圆长轴和短轴的端点
            Point long_p1 = calMidpoint(pts[0], pts[3]);
            Point long_p2 = calMidpoint(pts[1], pts[2]);
            Point short_p1 = calMidpoint(pts[0], pts[1]);
            Point short_p2 = calMidpoint(pts[2], pts[3]);
			// 画出径线
			// Tips:这种方法不适合特定场景,且径线的端点也不一点落在病灶边界上
            line(img, long_p1, long_p2, Scalar(0, 0, 255));
            line(img, short_p1, short_p2, Scalar(0, 0, 255));
            // 根据边界计算病灶面积
            double area = contourArea(cnts[max_index]);
            // 根据边界计算,闭合边界的周长
            double length = arcLength(cnts[max_index], true);
            cout << "area:" << area << ", length" << length << endl;
            // 根据边界画出轮廓
            drawContours(img, cnts, max_index, Scalar(0, 255, 0));
            imshow("processed", img);
            waitKey(0);
        } else {
            cout << "no contours." << endl;
        }
    }
    catch (cv::Exception &e) {
        const char* err_msg = e.what();
        cout << "exception caught: " << err_msg << endl;
    }
}

效果

一个案例展示opencv在医学图像病灶分割任务的常用操作

上一篇:【SCC】Proving Equivalences UVALive - 4287


下一篇:OpenCV 答题卡识别