单目结构光——Micro deep learning profilometry for 3D surface imaging 复现

原理

   监督集构造

        由12步相移获得的包裹相位如下图所示:

   

   存在两个情况:

          一是右上角质量比较差,出现很多噪点;二是相位截断处的质量不高,边界不整齐。

  调整思路1:

                   1)增强亮度

                   2)换成32步相移(实验感觉12步已经足够了)

                   3)找到合适的波长

   ---------------------------------------------------------------------------------------  

   出现噪点的原因很大概率是由于景深导致的周边模糊,我调节了镜头焦距,发现不能使整个场景都清晰,因为相机是倾向放置的,景深范围不够确实会导致这样的问题。

   光圈越大,景深越小;光圈越小,景深越大

   调整思路2:

          调小光圈,使用白光 

   ---------------------------------------------------------------------------------------

  深度学习框架复现

  参数

  W,H为原始图像的宽,高

  convolutional layers:kernel:3 × 3, stride:1,Zero-padding,C = 50

  max-pooling layer: 2 × 2 or 4 × 4

  激活函数:Relu

  损失函数:

  优化:Adam Optimizer

  初始学习率:10^-4,如果验证损失在10个时期内停止改善,我们将其减少2倍

  原始条纹图像{ I 1 ( x,y),I 2 ( x,y),I 3 ( x,y )}被除以255进行归一化,这可以使网络的学习过程更容易

  卷积层

def conv3x3(in_channels, out_channels, stride=1):
    return nn.Conv2d(in_channels, out_channels, kernel_size=3,
                     stride=stride, padding=1, bias=False, padding_mode='replicate')

  池化层

  残差Block

  上采样层

   数据首先通过一个带有ReLU激活的卷积层。然后我们使用四重滤波器从输入中提取特征,为后续的上采样提供丰富的信息

  连接层

import os
import cv2
import numpy as np
import random


def load_img(labelfile, imagesdir):
    with open(labelfile, "r") as f:
        lines = f.readlines()
        lines = [line.replace("\n", "") for line in lines]

    files = []
    for index, line in enumerate(lines):
        filesRelativePath = line.split(" ")
        filesAbsolutePath = [f"{imagesdir}/{frp}" for frp in filesRelativePath]
        files.append(filesAbsolutePath)

    return files


def imread(path):
    return cv2.imdecode(np.fromfile(path, dtype=np.uint8), 1)


def mkdirs_from_file_path(path):
    try:
        path = path.replace("\\", "/")
        p0 = path.rfind('/')
        if p0 != -1:
            path = path[:p0]

            if not os.path.exists(path):
                os.makedirs(path)

    except Exception as e:
        print(e)


def randrf(low, high):
    return random.uniform(0, 1) * (high - low) + low
import torch
import torch.nn as nn
import torchvision.transforms.functional as T
import numpy as np


class mseLoss(nn.Module):
    def forward(self, fringe1Real, fringe1Image, fringe2Real,fringe2Image,fringe3Real,fringe3Image,
                pred1Real, pred1Image, pred2Real, pred2Image, pred3Real, pred3Image):

        metric_loss = torch.nn.MSELoss(size_average=None, reduce=None, reduction='mean')
        loss1 = metric_loss(fringe1Real, pred1Real)
        loss2 = metric_loss(fringe1Image, pred1Image)
        loss3 = metric_loss(fringe2Real, pred2Real)
        loss4 = metric_loss(fringe2Image, pred2Image)
        loss5 = metric_loss(fringe3Real, pred3Real)
        loss6 = metric_loss(fringe4Image, pred4Image)

        loss = loss1 + loss2 + loss3 + loss4 + loss5 + loss6

        return loss/6
import torch
import torch.nn as nn
import random
import numpy as np

def conv3x3(in_channels, out_channels, stride=1):
    return nn.Conv2d(in_channels, out_channels, kernel_size=3,
                     stride=stride, padding=1, bias=False, padding_mode='replicate')

class conv(nn.Module):
    def __init__(self, in_channels, out_channels, stride=1):
        super(conv, self).__init__()

        self.conv1 = conv3x3(in_channels, out_channels, stride)
        self.bn1 = nn.BatchNorm2d(out_channels)
        self.act = nn.ReLU(inplace=False)

    def forward(self, x):
        out = self.conv1(x)
        out = self.bn1(out)
        out = self.act(out)
        return out


# Up Sample Module
# 双线性插值Bilinear+Conv+BN+Activation的上采样模块,优于用反卷积、最近邻插值等,所以比较推荐
class UpModule(nn.Module):
    def __init__(self, in_channels, out_channels, kernel_size=2, stride=2, bias=False, mode="UCBA"):
        super(UpModule, self).__init__()
        self.mode = mode

        if self.mode == "UCBA":
            self.up = nn.UpsamplingBilinear2d(scale_factor=2)
            self.conv = CBAModule(in_channels, out_channels, 3, padding=1, bias=bias)
        elif self.mode == "DeconvBN":
            self.dconv = nn.ConvTranspose2d(in_channels, out_channels, kernel_size, stride, bias=bias)
            self.bn = nn.BatchNorm2d(out_channels)
        elif self.mode == "DeCBA":
            self.dconv = nn.ConvTranspose2d(in_channels, out_channels, kernel_size, stride, bias=bias)
            self.conv = CBAModule(out_channels, out_channels, 3, padding=1, bias=bias)
        else:
            raise RuntimeError(f"Unsupport mode: {mode}")

    def forward(self, x):
        if self.mode == "UCBA":
            return self.conv(self.up(x))
        elif self.mode == "DeconvBN":
            return F.relu(self.bn(self.dconv(x)))
        elif self.mode == "DeCBA":
            return self.conv(self.dconv(x))

# SENet SANet
class SeModule(nn.Module):
    def __init__(self, in_size, reduction=4):
        super(SeModule, self).__init__()

        self.pool = nn.AdaptiveAvgPool2d(1)
        self.se = nn.Sequential(
            nn.Conv2d(in_size, in_size // reduction, kernel_size=1, stride=1, padding=0, bias=False),
            nn.BatchNorm2d(in_size // reduction),
            nn.ReLU(inplace=True),
            nn.Conv2d(in_size // reduction, in_size, kernel_size=1, stride=1, padding=0, bias=False),
            nn.BatchNorm2d(in_size),
            HSigmoid()
        )

    def forward(self, x):
        return x * self.se(self.pool(x))


class ResidualBlock(nn.Module):
    def __init__(self, in_channels, out_channels, stride=1, downsample=None):
        super(ResidualBlock, self).__init__()

        self.conv1 = conv3x3(in_channels, out_channels, stride)
        self.bn1 = nn.BatchNorm2d(out_channels)
        self.relu = nn.ReLU(inplace=False)
        self.conv2 = conv3x3(out_channels, out_channels)
        self.bn2 = nn.BatchNorm2d(out_channels)


    def forward(self, x):
        out = self.conv1(x)
        residual = out
        out = self.bn1(out)
        out = self.relu(out)
        out = self.conv2(out)
        out = self.bn2(out)
        out = out + residual
        out = self.relu(out)
        return out


class ResidualBlockUp(nn.Module):

    def __init__(self, in_channels, out_channels, stride=1):
        super(ResidualBlockUp, self).__init__()

        self.conv1 = conv3x3(in_channels, 2 * out_channels, stride)
        self.bn1 = nn.BatchNorm2d(2 * out_channels)
        self.relu = nn.ReLU(inplace=False)
        self.conv2 = conv3x3(2 * out_channels, out_channels)
        self.bn2 = nn.BatchNorm2d(out_channels)

    def forward(self, x):
        out = self.conv1(x)
        residual = out
        out = self.bn1(out)
        out = self.relu(out)
        out += residual
        out = self.conv2(out)
        out = self.bn2(out)
        out = self.relu(out)
        return out



class UDLP(torch.nn.Module):

    def __init__(self):
        super(UDLP, self).__init__()

        self.conv_3x3_1 = conv(1, 50)
        self.conv_3x3_2 = conv(50, 50)
        self.head = nn.Conv2d(150, 6, 1)
        self.max_pool_2x2 = nn.MaxPool2d(kernel_size=2, stride=2)
        self.max_pool_4x4 = nn.MaxPool2d(kernel_size=4, stride=4)
        self.Resblock1 = ResidualBlock(50, 50)
        self.up_trans_1 = nn.ConvTranspose2d(
            in_channels=50,
            out_channels=50,
            kernel_size=2,
            stride=2)

    def forward(self, image):
        # ch1
        x1_1 = self.conv_3x3_1(image[:,0,:,:].unsqueeze(0))
        x1_2 = self.Resblock1(x1_1)
        x1_3 = self.conv_3x3_2(x1_2)

        # ch2
        x2_1 = self.conv_3x3_1(image[:,1,:,:].unsqueeze(0))
        x2_2 = self.max_pool_2x2(x2_1)
        x2_3 = self.Resblock1(x2_2)
        x2_4 = self.up_trans_1(x2_3)
        x2_5 = self.conv_3x3_2(x2_4)

        # ch3
        x3_1 = self.conv_3x3_1(image[:,2,:,:].unsqueeze(0))
        x3_2 = self.max_pool_4x4(x3_1)
        x3_3 = self.Resblock1(x3_2)
        x3_4 = self.up_trans_1(x3_3)
        x3_5 = self.up_trans_1(x3_4)
        x3_6 = self.conv_3x3_2(x3_5)

        x = torch.cat([x1_3, x2_5, x3_6], 1)  # 150*W*H

        x = self.head(x) # 6*W*H

        return x



if __name__ == "__main__":
    image = torch.rand((1, 3, 256, 256))
    model = UDLP()
    print(model(image).size())
from torch.utils.data import Dataset
import torch
import torch.nn as nn
import torchvision.transforms.functional as T
import UDLP
import common
import loss
import loger


class MyDataset(Dataset):
    def __init__(self, labelfile, imagesdir):
        self.items = load_img(labelfile, imagesdir)

    def __getitem__(self, index):
        imgs = self.items[index]
        fringe1 = common.imread(imgs[0])
        fringe2 = common.imread(imgs[1])
        fringe3 = common.imread(imgs[2])
        fringe1Real = common.imread(imgs[3])
        fringe1Image = common.imread(imgs[4])
        fringe2Real = common.imread(imgs[5])
        fringe2Image = common.imread(imgs[6])
        fringe3Real = common.imread(imgs[7])
        fringe3Image = common.imread(imgs[8])

        fringe1 = (fringe1 / 255.0)
        fringe2 = (fringe2 / 255.0)
        fringe3 = (fringe3 / 255.0)
        fringe1Real = (fringe1Real / 255.0)
        fringe1Image = (fringe1Image / 255.0)
        fringe2Real = (fringe2Real / 255.0)
        fringe2Image = (fringe2Image / 255.0)
        fringe3Real = (fringe3Real / 255.0)
        fringe3Image = (fringe3Image / 255.0)

        return T.to_tensor(fringe1), T.to_tensor(fringe2), T.to_tensor(fringe3), \
               T.to_tensor(fringe1Real), T.to_tensor(fringe1Image), T.to_tensor(fringe2Real), \
               T.to_tensor(fringe2Image),T.to_tensor(fringe3Real), T.to_tensor(fringe3Image)

    def __len__(self):
        return len(self.items)


class App(object):
    def __init__(self, labelfile, imagesdir):
        self.batch_size = 18
        self.lr = 1e-4
        self.gpus = [0, 1, 2, 3]  # [0, 1, 2, 3]
        self.gpu_master = self.gpus[0]
        self.model = UDLP()
        self.model.init_weights() # how to init weights?
        self.model.cuda(device=self.gpu_master)
        self.model.train()
        self.mse_loss = losses.mseLoss()
        self.train_dataset = MyDataset(labelfile, imagesdir)
        self.train_loader = DataLoader(dataset=self.train_dataset, batch_size=self.batch_size, shuffle=True, num_workers=24)
        self.optimizer = torch.optim.Adam(self.model.parameters(), lr=self.lr)
        self.per_epoch_batchs = len(self.train_loader)
        self.iter = 0
        self.epochs = 150


    def set_lr(self, lr):
        self.lr = lr
        log.info(f"setting learning rate to: {lr}")
        for param_group in self.optimizer.param_groups:
            param_group["lr"] = lr

    def train_epoch(self, epoch):
        for indbatch, (fringe1, fringe2, fringe3, fringe1Real, fringe1Image, fringe2Real,fringe2Image,fringe3Real,fringe3Image) in enumerate(self.train_loader):
            self.iter += 1
            batch_size = self.batch_size
            images = torch.cat([fringe1, fringe2, fringe3], 0)
            pred1Real, pred1Image, pred2Real, pred2Image, pred3Real, pred3Image = self.model(images)
            loss = mse_loss(fringe1Real, fringe1Image, fringe2Real,fringe2Image,fringe3Real,fringe3Image,
                            pred1Real, pred1Image, pred2Real, pred2Image, pred3Real, pred3Image)

            self.optimizer.zero_grad()
            loss.backward()
            self.optimizer.step()

            epoch_flt = epoch + indbatch / self.per_epoch_batchs

            if indbatch % 10 == 0:
                log.info(f"iter: {self.iter}, lr: {self.lr:g}, epoch: {epoch_flt:.2f}, loss: {loss.item():.2f}")

    def train(self):

        lr_scheduer = {
            1: 1e-3,
            2: 2e-3,
            3: 1e-3,
            60: 1e-4,
            120: 1e-5
        }

        # train
        self.model.train()
        for epoch in range(self.epochs):

            if epoch in lr_scheduer:
                self.set_lr(lr_scheduer[epoch])

            self.train_epoch(epoch)
            file = f"{jobdir}/models/{epoch + 1}.pth"
            common.mkdirs_from_file_path(file)
            torch.save(self.model.module.state_dict(), file)

trial_name = "uDLP"
jobdir = f"jobs/{trial_name}"
log = logger.create(trial_name, f"{jobdir}/logs/{trial_name}.log")
app = App("webface/train/label.txt", "webface/WIDER_train/images")
app.train()


版权声明:本文为wuxianfeng1987原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。