- 💂 个人主页:风间琉璃
- 🤟 版权: 本文由【风间琉璃】原创、在CSDN首发、需要转载请联系博主
- 💬 如果文章对你有帮助、欢迎关注、点赞、收藏(一键三连)和订阅专栏哦
前言
Fully Convolutional Network(FCN)是一种深度学习架构,主要用于图像分割任务。FCN 架构的典型应用包括语义分割、实例分割、物体检测等图像分析任务。它已经成为了计算机视觉领域图像分割任务的重要工具,并在许多竞赛和实际应用中取得了出色的成绩。
一、FCN简介
通常CNN网络在卷积层之后会接上若干个全连接层, 将卷积层产生的特征图(feature map)映射成一个固定长度的特征向量。
以AlexNet为代表的经典CNN结构适合于图像级的分类和回归任务,因为它们最后都期望得到整个输入图像的一个数值描述(概率),比如AlexNet的ImageNet模型输出一个1000维的向量表示输入图像属于每一类的概率(softmax归一化)。
下图中的猫, 输入AlexNet, 得到一个长为1000的输出向量, 表示输入图像属于每一类的概率, 其中在“tabby cat”这一类统计概率最高。
FCN对图像进行像素级的分类,从而解决了语义级别的图像分割(semantic segmentation)问题。与经典的CNN在卷积层之后使用全连接层得到固定长度的特征向量进行分类(全联接层+softmax输出)不同,FCN可以接受任意尺寸的输入图像,采用反卷积层对最后一个卷积层的feature map进行上采样, 使它恢复到输入图像相同的尺寸,从而可以对每个像素都产生了一个预测, 同时保留了原始输入图像中的空间信息, 最后在上采样的特征图上进行逐像素分类。
最后逐个像素计算softmax分类的损失, 相当于每一个像素对应一个训练样本。下图是Longjon用于语义分割所采用的全卷积网络(FCN)的结构示意图:
所以,FCN与CNN的区域在把于CNN最后的全连接层换成卷积层,输出的是一张已经Label好的图片。
FCN原理: FCN将传统卷积网络后面的全连接层换成卷积层,这样网络输出不再是类别而是heatmap,同时为了解决因为卷积和池化对图像尺寸的影响,提出使用上采样的方式恢复。
核心思想:
- - 不含全连接层(fc)的全卷积(fully conv)网络。可适应任意尺寸输入。
- - 增大数据尺寸的反卷积(deconv)层。能够输出精细的结果。
- - 结合不同深度层结果的跳级(skip)结构。同时确保鲁棒性和精确性。
网络结构详图如下,输入可为任意尺寸图像彩色图像,输出与输入尺寸相同,深度为:20类目标+背景=21。
二、加载网络模型
这里使用Caffe深度学习框架中已经预训练好的FCN网络,需要相应的模型权重文件(.caffemodel)以及模型配置文件(.prototxt)。
加载模型和配置文件如下所示:
String model = "F:/data/CQU/VS/FCN_Segment/fcn8s-heavy-pascal.caffemodel";
String config = "F:/data/CQU/VS/FCN_Segment/fcn8s-heavy-pascal.prototxt";//加载网络
Net net = readNetFromCaffe(config, model);
使用 CUDA进行加速添加下面两行代码:
//使用cuda加速
net.setPreferableBackend(cv::dnn::DNN_BACKEND_CUDA);
net.setPreferableTarget(cv::dnn::DNN_TARGET_CUDA_FP16);
三、预处理
将FCN网络对应的目标检测分类标签(20个类别)以及对应颜色调入内存这样便于访问它们,通常这些类别信息存储在txt文件中。
String label = "F:/data/CQU/VS/FCN_Segment/pascal-classes.txt";//读取分割对象颜色
vector<Vec3b> readColor(String labelpath)
{vector<Vec3b> colors;ifstream fp(labelpath);if (!fp.is_open()){printf("could not open the file...\n");exit(-1);}string line;while (!fp.eof()) {//读取每一行getline(fp, line); if (line.length()) {stringstream ss(line);string name;ss >> name; //分割对象int temp;Vec3b color; //依次读取三个通道的颜色值大小ss >> temp;color[0] = (uchar)temp;ss >> temp;color[1] = (uchar)temp;ss >> temp;color[2] = (uchar)temp;colors.push_back(color);}}return colors;
}//读取标签颜色
vector<Vec3b> colors = readColor(label);
然后需要对输入图像进行预处理,主要要使输入图像尺寸满足网络输入的大小,网络输入的大小可以在配置文件prototxt中查看。
//图像预处理
resize(frame, frame, Size(500, 500));
Mat blobImage = blobFromImage(frame);
四、执行推理
图片预处理完成,就可以利用网络进行预测,这个过程也是把输入图像在网络各层中前向进行传播。
//构建输入
net.setInput(blobImage, "data");
//执行推理
Mat score = net.forward("score");
data和score是网络模型输入层和输出层的名称,可以网络配置文件prototxt中查看。
五、解析输出
在score中存储着网络的所有输出,还需要对score进行后处理,然后将分割结果显示。
//分割并显示
const int chns = score.size[1];
const int rows = score.size[2];
const int cols = score.size[3];
//每个像素的最大类别(通道)
Mat maxCl(rows, cols, CV_8UC1);
//每个像素的最大值
Mat maxVal(rows, cols, CV_32FC1);//LUT查找
for(int c = 0; c < chns; c++)
{for(int row = 0; row < rows; row++) {//获取每个分割对象的置信度const float* ptrScore = score.ptr<float>(0, c, row);uchar* ptrMaxCl = maxCl.ptr<uchar>(row);float* ptrMaxVal = maxVal.ptr<float>(row);for(int col = 0; col < cols; col++) {//分割结果大于最大值if (ptrScore[col] > ptrMaxVal[col]) {ptrMaxVal[col] = ptrScore[col];ptrMaxCl[col] = (uchar)c;}}}
}//为每个像素分配一个对应的颜色,并将结果存储在result矩阵
Mat result = Mat::zeros(rows, cols, CV_8UC3);
for (int row = 0; row < rows; row++)
{const uchar* ptrMaxCl = maxCl.ptr<uchar>(row);Vec3b* ptrColor = result.ptr<Vec3b>(row);for (int col = 0; col < cols; col++) {ptrColor[col] = colors[ptrMaxCl[col]];}
}
Mat dst;
imshow("FCN", result);
//将两个图像按照一定的权重进行线性叠加,加入背景
//frame,result尺寸要相同
addWeighted(frame, 0.3, result, 0.7, 0, dst);
imshow("Src-FCN", dst);
运行结果:
源码:资源下载:OpenCVFCN图像分割资源-CSDN文库
// FCN_Segment.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//#include <iostream>
#include <opencv2/opencv.hpp>
#include <opencv2/dnn.hpp>
#include <opencv2/highgui/highgui_c.h>
#include <vector>
#include <fstream>using namespace cv;
using namespace cv::dnn;
using namespace std;const size_t width = 500;
const size_t height = 500;
String label = "F:/data/CQU/VS/FCN_Segment/pascal-classes.txt";
String model = "F:/data/CQU/VS/FCN_Segment/fcn8s-heavy-pascal.caffemodel";
String config = "F:/data/CQU/VS/FCN_Segment/fcn8s-heavy-pascal.prototxt";//读取分割对象颜色
vector<Vec3b> readColor(String labelpath)
{vector<Vec3b> colors;ifstream fp(labelpath);if (!fp.is_open()){printf("could not open the file...\n");exit(-1);}string line;while (!fp.eof()) {//读取每一行getline(fp, line); if (line.length()) {stringstream ss(line);string name;ss >> name; //分割对象int temp;Vec3b color; //依次读取三个通道的颜色值大小ss >> temp;color[0] = (uchar)temp;ss >> temp;color[1] = (uchar)temp;ss >> temp;color[2] = (uchar)temp;colors.push_back(color);}}return colors;
}int main()
{Mat frame = imread("F:/data/CQU/VS/FCN_Segment/luo.jpg");if (frame.empty()) {printf("could not load image...\n");return -1;}namedWindow("src", CV_WINDOW_AUTOSIZE);imshow("src", frame);//图像预处理resize(frame, frame, Size(500, 500));Mat blobImage = blobFromImage(frame);//读取标签颜色vector<Vec3b> colors = readColor(label);//加载网络Net net = readNetFromCaffe(config, model);//使用cuda加速net.setPreferableBackend(cv::dnn::DNN_BACKEND_CUDA);net.setPreferableTarget(cv::dnn::DNN_TARGET_CUDA_FP16);//构建输入net.setInput(blobImage, "data");//执行推理Mat score = net.forward("score");//分割并显示const int chns = score.size[1];const int rows = score.size[2];const int cols = score.size[3];//每个像素的最大类别(通道)Mat maxCl(rows, cols, CV_8UC1);//每个像素的最大值Mat maxVal(rows, cols, CV_32FC1);//LUT查找for(int c = 0; c < chns; c++){for(int row = 0; row < rows; row++) {//获取每个分割对象的置信度const float* ptrScore = score.ptr<float>(0, c, row);uchar* ptrMaxCl = maxCl.ptr<uchar>(row);float* ptrMaxVal = maxVal.ptr<float>(row);for(int col = 0; col < cols; col++) {//分割结果大于最大值if (ptrScore[col] > ptrMaxVal[col]) {ptrMaxVal[col] = ptrScore[col];ptrMaxCl[col] = (uchar)c;}}}}//为每个像素分配一个对应的颜色,并将结果存储在result矩阵Mat result = Mat::zeros(rows, cols, CV_8UC3);for (int row = 0; row < rows; row++) {const uchar* ptrMaxCl = maxCl.ptr<uchar>(row);Vec3b* ptrColor = result.ptr<Vec3b>(row);for (int col = 0; col < cols; col++) {ptrColor[col] = colors[ptrMaxCl[col]];}}Mat dst;imshow("FCN", result);//将两个图像按照一定的权重进行线性叠加,加入背景//frame,result尺寸要相同addWeighted(frame, 0.3, result, 0.7, 0, dst);imshow("Src-FCN", dst);waitKey(0);return 0;
}
结束语
感谢你观看我的文章呐~本次航班到这里就结束啦 🛬
希望本篇文章有对你带来帮助 🎉,有学习到一点知识~
躲起来的星星🍥也在努力发光,你也要努力加油(让我们一起努力叭)。
最后,博主要一下你们的三连呀(点赞、评论、收藏),不要钱的还是可以搞一搞的嘛~
不知道评论啥的,即使扣个666也是对博主的鼓舞吖 💞 感谢 💐