位置: IT常识 - 正文

BasicSR的使用过程(basic run)

编辑:rootadmin
BasicSR的使用过程

推荐整理分享BasicSR的使用过程(basic run),希望有所帮助,仅作参考,欢迎阅读内容。

文章相关热门搜索词:basics用法,basic species,basic模式下不能使用范围,basic s,basic stain,basic stain,basic use,basic use,内容如对您有帮助,希望把文章链接给更多的朋友!

        由于最近在研究BasicSR的超分辨率的各种模型,在测试各种实验的过程中出现过很多的问题,下面针对这些问题说一下解决方式。比如像我本次遇到的问题,当输入的图像数据特别小时我们应该如何处理。本文仅针对使用BasicSR进行一个模型使用和模型对比的安装、使用、一些注意实现进行一部分的描述。

 1、安装

王鑫涛大佬的文档中给出了安装的教程,由于可能有很多同学不是很喜欢去下载官方的文档来进行阅读,所以在此处我在重新写一下安装的过程。并且说一下这两种安装方式的好处和缺点。

BasicSR是基于pytorch深度学习框架开发的。所以那些只看过tensorflow的同学们建议稍稍的看一下pytorch的API接口文档(PyTorch documentation — PyTorch 1.13 documentation),在来阅读他的文档可能要更方便。如果想要在GPU上运行的话,需要提前在电脑上配置相应的CUDA环境。但是由于本人的电脑没有GPU,所以用的是服务器已经配置好的环境。所以配置CUDA大家可以Google一下教程,此处并不麻烦。

本地clone代码安装(对于本地想测试多个模型且想研究并修改源码的同学建议安装)

1. 克隆项目:         git clone https://github.com/XPixelGroup/BasicSR.git

2. 安装依赖包:

        此环境的依赖包的版本需求已经整理到了requirements.txt中。

cd BasicSRpip install -r requirements.txt

3. 在 BasicSR 的根目录下安装 BasicSR:

python setup.py develop

如果希望安装的时候指定 CUDA 路径,可使用如下指令:(当然也可以在程序运行去手动指定所以这一步可选)

CUDA_HOME=/usr/local/cuda \CUDNN_INCLUDE_DIR=/usr/local/cuda \CUDNN_LIB_DIR=/usr/local/cuda \python setup.py developpip方式安装

对于使用 pip 安装 BasicSR,在终端上运行以下指令即可:

pip install basicsr

如果希望安装的时候指定 CUDA 路径,可使用如下指令:

CUDA_HOME=/usr/local/cuda \CUDNN_INCLUDE_DIR=/usr/local/cuda \CUDNN_LIB_DIR=/usr/local/cuda \pip install basicsr

验证 BasicSR 是否安装成功

 如果此时没有报错,则说明 BasicSR 安装成功,此时便可以基于 BasicSR 进行开发啦 ∼∼∼

 2、目录基本解读

王鑫涛大佬的这一部分写的还是比较详细的。此处我会根据我的理解和这几天的使用情况,描述一下大家使用时候的注意事项。

红色 表示和跑实验直接相关的文件,即我们平时打交道最多的文件;

蓝色 表示其他与 BasicSR 强相关的代码文件;

黑色 表示配置文件。

在 BasicSR 仓库中,核心代码在 basicsr 这个文件夹中。这个部分主要为深度学习模型常用的 代码文件,比如网络结构,损失函数和数据加载等,具体目录如下。

BasicSR的使用过程(basic run)

其中,红色 表示我们在开发中主要修改的文件。

 

(此处应该注意一下,为什么呢? 上述的红色文件的确是咱们可以进行修改的地方,但是对于图像的尺寸,在他的代码里有着严格的要求。所以修改时一定要慎重!所以修改时一定要慎重!所以修改时一定要慎重!)

由于在算法设计和开发中,还需要用到一些脚本,比如数据的预处理、指标计算等,相关的文 件位于scripts,目录如下

 上述的scripts中基本就是对数据的一个准备,可以进行数据下载和很多的数据模型的下载。但是有个问题是里面的部分数据预训练模型可能在国内无法访问。需要翻墙,所以需要寻找其他的资源。

但是吧,在这个脚本里可能有很多不能解决我们真实要处理的问题,所以在处理阶段的数据脚本我们可以自行在这里面编写。

3、数据预处理

本人此次使用的图片过小,所以此处直接使用他的ESRGAN的超分辨率模型出现了尺寸的问题。 所以在此处我只能对我现在的小尺寸的数据进行resize。

有同学可能说:Resize难道不会损失图片的精度吗?是的。我当时也有这个问题,然后我实验了一下。再此呢我们只简单的提一下我是怎么进行测试的,大家可以参考一下我的思路。我是将他的图片进行了一个放大,用的是

        1. Bicubic (双三次插值)对图像进行缩放(为啥要用双三次差值? 为了方便大家查阅 我直接把他的优缺点整理在下面了,包括他的双三次插值算法)

        2. 通过cv2中的resize() 对1中放大的图片进行缩放

        3. 通过对比PSNR和SSIM的值来判断效果如何

# 双三次插值算法 img:图像,dstH:目标图像的高 dstW: 目的图像的宽def BiCubic_interpolation(img, dstH, dstW): scrH, scrW, _= img.shape retimg = np.zeros((dstH, dstW, 3), dtype=np.uint8) for i in range(dstH): for j in range(dstW): scrx = i*(scrH/dstH) scry = j*(scrW/dstW) x = math.floor(scrx) # floor向下取整 y = math.floor(scry) u = scrx-x v = scry-y tmp = 0 for ii in range(-1, 2): for jj in range(-1, 2): if x+ii < 0 or y+jj < 0 or x+ii >= scrH or y+jj >= scrW: continue tmp += img[x+ii, y+jj] * BiBubic(ii-u) * BiBubic(jj-v) retimg[i, j] = np.clip(tmp, 0, 255) return retimg

在此处我们简单的提一下什么的效果最好,也说一下我是怎么做的这部分。由于王鑫涛大佬当时写这一部分的时候面向的对象就是对于人物或者是其他高清的图片所以说此处利用它例子给的图片或者是普通的图片都是可以的。因为他在自己的ESR的配置模型中给出的配置文件使用的128作为了图像裁剪的尺寸。为啥他要这么做呢?它提供的图片或者是咱们日常用于测试的图片一般分辨率会比较大,像此模型提供的数据集就是2048x1024的!发现问题了吗?是的,数据给的都是比较大的。所以这就引出了他为啥要进行裁剪。而我们在训练的时候往往并不要那么大 (常见的是 128×128 或者 192×192 的训练 patch). 因此我们 可以先把 2K 的图片裁剪成有 overlap 的 480×480 的子图像块. 然后再由 dataloader 从这 个 480×480 的子图像块中随机 crop 出 128×128(ESR) 或者 192×192 (EDSR)的训练 patch。

大家可能在上述的描述中理解力可能有和我一样的同学,在没有进行他的实验的时候是没有办法没有彻底清楚他的流程。那我在此处用白话的形式给大家形容一下它到底想表达什么。他的根本意思其实是想表达,当我们输入一个比较大的图像的时候为了不给显存带来过大的负载,那怎么办呢?我们就可以先把她这个图像给他切割成多个图像,就比如一个拼图的碎片,我们每次处理只是去处理一个碎片,那我们切割的时候是不是说图片可能长宽都不一样对吧?那我们怎么去处理这个问题呢,就是它上面说的overlap了。所以说即便重叠是没有关系的。所以说它通过整个图片去整除他的gt_size除不尽是没有关系的。然后咱们对他的sub图片采用的是同一个策略。

最后这一部分我说一下我现在碰到的问题也就是开头我们碰到的情况,顺便给大家避个雷。如果说我们输入的图片他并不是一个生活中常见的图片大小,而是在处理一个图片已经被分割的特别小的图片了,大小只有几KB,像素只有72x28、36x63、等等此类的特别小的。所以我们放到他这个模型里面没有办法直接使用她这个ESRGAN的网络模型,其实咱们从他的名字也看出来了,他给的例子是用的2K的数据集进行操作的。所以我们要怎么处理这个问题呢?我当时在做这一部分的时候,抱着移山大法的心态去做的!反正我的数据没有错我就不改,我就去改他的代码,然后我就去他的basicsr目录下去改他的model了,为了适配我的尺寸,前前后后改了特别多的东西。但是即便有部分改成功了但是最后还是有一些特别小的图片还是没有办法实现我的最小图片。所以我就想能不能把她的crop的function给删除掉!于是我又冲了,结果也很明显,失败了(虽然本人比较菜,但这一次其实他的框架的TODO中也说了他那一部分写的也不是很好,所以我当时也没有办法在短期内给他改出来更好的方法)。结果前前后后躺了比较多的坑本人精神状态已经不太好了。终于终于转机出现了———————— 最后我的导师给我出了几个点子,我豁然开朗。

微小图片的解决办法:其实一开始我便进入了一个误区就是我一直在想改变别人的东西来适配自己,但是换换思路,去改变我们的数据集其实更容易解决。所以我就将我们的图片数据进行了一个修改,修改到了正常图片的大小,也就是要大于他给的ESRGAN配置文件中的gt_size。(这个gt_size真的不要随便改,因为他的输入和卷积里面在py中写死了,并不是动态修改的,所以非常容易在其中某一步出现问题。)然后我们的图片到了正常的大小就可以进行接下来的操作了。是不是在这想问放大之后会不会对图片的精度产生影响?没问题,刚刚在上面已经解释过了。所以大家在经过自己的PSNR或者是SSIM的测试之后,可以放心的使用。从这一部分开始我们的数据准备阶段就算是正式完成了。

此处给出以下他官方的处理sub文件的脚本和对于小图片的处理:

import cv2import numpy as npimport osimport sysfrom multiprocessing import Poolfrom os import path as ospfrom tqdm import tqdmfrom basicsr.utils import scandirdef main(): """A multi-thread tool to crop large images to sub-images for faster IO. It is used for DIV2K dataset. Args: opt (dict): Configuration dict. It contains: n_thread (int): Thread number. compression_level (int): CV_IMWRITE_PNG_COMPRESSION from 0 to 9. A higher value means a smaller size and longer compression time. Use 0 for faster CPU decompression. Default: 3, same in cv2. input_folder (str): Path to the input folder. save_folder (str): Path to save folder. crop_size (int): Crop size. step (int): Step for overlapped sliding window. thresh_size (int): Threshold size. Patches whose size is lower than thresh_size will be dropped. Usage: For each folder, run this script. Typically, there are four folders to be processed for DIV2K dataset. * DIV2K_train_HR * DIV2K_train_LR_bicubic/X2 * DIV2K_train_LR_bicubic/X3 * DIV2K_train_LR_bicubic/X4 After process, each sub_folder should have the same number of subimages. Remember to modify opt configurations according to your settings. """ opt = {} opt['n_thread'] = 10 opt['compression_level'] = 3 # HR images opt['input_folder'] = '../../datasets/example/BSDS100' # 数据图片的源文件 opt['save_folder'] = '../../datasets/example/BSDS100_sub' # 数据图片的压缩之后的文件夹 opt['crop_size'] = 120 opt['step'] = 240 opt['thresh_size'] = 1 extract_subimages(opt)def extract_subimages(opt): """Crop images to subimages. Args: opt (dict): Configuration dict. It contains: input_folder (str): Path to the input folder. save_folder (str): Path to save folder. n_thread (int): Thread number. """ input_folder = opt['input_folder'] save_folder = opt['save_folder'] if not osp.exists(save_folder): os.makedirs(save_folder) print(f'mkdir {save_folder} ...') else: print(f'Folder {save_folder} already exists. Exit.') sys.exit(1) img_list = list(scandir(input_folder, full_path=True)) pbar = tqdm(total=len(img_list), unit='image', desc='Extract') pool = Pool(opt['n_thread']) for path in img_list: pool.apply_async(worker, args=(path, opt), callback=lambda arg: pbar.update(1)) pool.close() pool.join() pbar.close() print('All processes done.')def worker(path, opt): """Worker for each process. Args: path (str): Image path. opt (dict): Configuration dict. It contains: crop_size (int): Crop size. step (int): Step for overlapped sliding window. thresh_size (int): Threshold size. Patches whose size is lower than thresh_size will be dropped. save_folder (str): Path to save folder. compression_level (int): for cv2.IMWRITE_PNG_COMPRESSION. Returns: process_info (str): Process information displayed in progress bar. """ crop_size = opt['crop_size'] step = opt['step'] thresh_size = opt['thresh_size'] img_name, extension = osp.splitext(osp.basename(path)) # remove the x2, x3, x4 and x8 in the filename for DIV2K img_name = img_name.replace('x2', '').replace('x3', '').replace('x4', '').replace('x8', '') img = cv2.imread(path, cv2.IMREAD_UNCHANGED) h, w = img.shape[0:2] h_space = np.arange(0, h - crop_size + 1, step) if h - (h_space[-1] + crop_size) > thresh_size: h_space = np.append(h_space, h - crop_size) w_space = np.arange(0, w - crop_size + 1, step) if w - (w_space[-1] + crop_size) > thresh_size: w_space = np.append(w_space, w - crop_size) index = 0 for x in h_space: for y in w_space: index += 1 cropped_img = img[x:x + crop_size, y:y + crop_size, ...] cropped_img = np.ascontiguousarray(cropped_img) cv2.imwrite( osp.join(opt['save_folder'], f'{img_name}_s{index:03d}{extension}'), cropped_img, [cv2.IMWRITE_PNG_COMPRESSION, opt['compression_level']]) process_info = f'Processing {img_name} ...' return process_infoif __name__ == '__main__': main()

然后给出对于小文件处理的sub脚本:

import osfrom os import path as ospimport cv2def scandir(dir_path, suffix=None, recursive=False, full_path=False): """Scan a directory to find the interested files. Args: dir_path (str): Path of the directory. suffix (str | tuple(str), optional): File suffix that we are interested in. Default: None. recursive (bool, optional): If set to True, recursively scan the directory. Default: False. full_path (bool, optional): If set to True, include the dir_path. Default: False. Returns: A generator for all the interested files with relative paths. """ if (suffix is not None) and not isinstance(suffix, (str, tuple)): raise TypeError('"suffix" must be a string or tuple of strings') root = dir_path def _scandir(dir_path, suffix, recursive): for entry in os.scandir(dir_path): if not entry.name.startswith('.') and entry.is_file(): if full_path: return_path = entry.path else: return_path = osp.relpath(entry.path, root) if suffix is None: yield return_path elif return_path.endswith(suffix): yield return_path else: if recursive: yield from _scandir(entry.path, suffix=suffix, recursive=recursive) else: continue return _scandir(dir_path, suffix=suffix, recursive=recursive )pnglist = list(scandir("../../datasets/example/bai_hr", full_path=True))print(pnglist)#读取原始图像for i in pnglist: img = cv2.imread(i,1) x, y, p = img.shape[0:3] #图像向下取样 r = cv2.pyrDown(img) r = cv2.pyrDown(r) r = cv2.resize(r, [int(y/4),int(x/4)]) # 为啥是4倍呢 因为ESRGAN的配置文件有一个scale的四倍放大 当然这个地方个人可以根据实际的需求处理 此处的参数并不会影响程序运行 cv2.imwrite("../../datasets/example/bai_hr_sub/" + str(i).split("/")[-1], r) 4、运行整个训练模型

        其实对于我们单纯的训练或者是做模型对比的来说到此处基本已经可以了,如果你不是想去修改他的模型或者是觉得他写的不够完美想继续完善他的框架。其实是咱们的准备工作都已经完成了。接下来我们可以直接跑咱们的训练模型了。

我们需要到我们的文件夹下来进行跑咱们的训练脚本:

cd BasicSR-master# 已经在第一步设置了cuda的或者对于自己的gpu可以自由支配的可以使用此运行命令python3 basicsr/train.py -opt options/train/ESRGAN/train_ESRGAN_x4.yml# 指定我们的gpu来跑训练模型CUDA_VISIBLE_DEVICES=2 python3 basicsr/train.py -opt options/train/ESRGAN/train_ESRGAN_x4.yml# 如果不知道自己的gpu信息可以安装一下gpustat 然后在自己的gpu中直接输入就可以看到了pip install gpustat

最终出现以下图片就算是训练完成了

 最后会在我们的experiments下面生成我们的训练结果

最后我们根据我们的峰值信噪比判别一下效果如何,我们的峰值信噪比可以在log中看到效果。 但是呢,他的PSNR可能并不是最符合咱们的评判标准。还是需要考虑其他的Loss判别。

完成!

本文链接地址:https://www.jiuchutong.com/zhishi/267826.html 转载请保留说明!

上一篇:iphone手机设置静音并且不振动的方法(怎么设置静音iphone)

下一篇:Win11怎么设置桌面任务栏置顶?(win11怎么设置桌面显示我的电脑)

  • 红米k30液冷散热怎么开启(k30p液冷散热)

    红米k30液冷散热怎么开启(k30p液冷散热)

  • 抖音互赞会不会有影响呢(抖音互赞会影响流量吗)

    抖音互赞会不会有影响呢(抖音互赞会影响流量吗)

  • 抖音怎么在直播间点赞(抖音怎么在直播间隐藏名字)

    抖音怎么在直播间点赞(抖音怎么在直播间隐藏名字)

  • 微信小程序位置权限怎么打开(微信小程序位置显示错误,如何纠正)

    微信小程序位置权限怎么打开(微信小程序位置显示错误,如何纠正)

  • 苹果xr快充支持多少瓦(苹果xr快充支持20w吗)

    苹果xr快充支持多少瓦(苹果xr快充支持20w吗)

  • oppo手机如何关机重启(oppo手机如何关闭锁屏杂志)

    oppo手机如何关机重启(oppo手机如何关闭锁屏杂志)

  • pr比特率设置多少合适(pr比特率设置多少不会二次压缩)

    pr比特率设置多少合适(pr比特率设置多少不会二次压缩)

  • 咪咕音乐为什么不能播放(咪咕音乐为什么可以免费听周杰伦)

    咪咕音乐为什么不能播放(咪咕音乐为什么可以免费听周杰伦)

  • 苹果手机怎么复制不了文字(苹果手机怎么复制文字)

    苹果手机怎么复制不了文字(苹果手机怎么复制文字)

  • 计算机里的e是什么意思(计算机里面e)

    计算机里的e是什么意思(计算机里面e)

  • 苹果11怎么控制空调(苹果11怎么控制美的空调)

    苹果11怎么控制空调(苹果11怎么控制美的空调)

  • 华为删除的视频还能找到吗(华为删除的视频和照片怎么恢复)

    华为删除的视频还能找到吗(华为删除的视频和照片怎么恢复)

  • 升级后的华为手机悬浮球设置在哪里(升级后的华为手机nova8如何关闭纯净模式)

    升级后的华为手机悬浮球设置在哪里(升级后的华为手机nova8如何关闭纯净模式)

  • 打印机充电辊故障表现(打印机充电辊故障怎么修)

    打印机充电辊故障表现(打印机充电辊故障怎么修)

  • 支付宝修改手机号会发短信吗(支付宝修改手机号码次数超限)

    支付宝修改手机号会发短信吗(支付宝修改手机号码次数超限)

  • 电脑屏幕反过来了怎么调整过来(电脑屏幕反过来了,按哪键正过来)

    电脑屏幕反过来了怎么调整过来(电脑屏幕反过来了,按哪键正过来)

  • 苹果总是自动关闭软件(苹果总是自动关机是什么原因)

    苹果总是自动关闭软件(苹果总是自动关机是什么原因)

  • 安卓flash怎么打开(flash player安卓版怎么用)

    安卓flash怎么打开(flash player安卓版怎么用)

  • 如何把qq状态弄成离线(如何把qq状态弄到桌面)

    如何把qq状态弄成离线(如何把qq状态弄到桌面)

  • 手机无法连接电脑是什么原因(手机无法连接电话网络怎么回事)

    手机无法连接电脑是什么原因(手机无法连接电话网络怎么回事)

  • 华为nova5可以长截屏吗(华为nova5手机怎么长截屏的4种方法)

    华为nova5可以长截屏吗(华为nova5手机怎么长截屏的4种方法)

  • 天猫魔盒卡顿怎么解决(天猫魔盒很卡怎么回事)

    天猫魔盒卡顿怎么解决(天猫魔盒很卡怎么回事)

  • 苹果11pro max后盖是玻璃的吗(苹果11promax后盖是什么材质)

    苹果11pro max后盖是玻璃的吗(苹果11promax后盖是什么材质)

  • oppor15x录屏功能在哪(oppor15x录屏怎么录声音)

    oppor15x录屏功能在哪(oppor15x录屏怎么录声音)

  • 华为p30pro耳机插上没反应(华为p30pro耳机插孔在哪)

    华为p30pro耳机插上没反应(华为p30pro耳机插孔在哪)

  • 三星s10是双卡双通么(三星s10是双卡双待手机吗)

    三星s10是双卡双通么(三星s10是双卡双待手机吗)

  • ps怎么抠图然后再拼接(ps怎么抠图然后放到另一张图片上)

    ps怎么抠图然后再拼接(ps怎么抠图然后放到另一张图片上)

  • 手机照片怎么弄成文件(手机照片怎么弄成pdf)

    手机照片怎么弄成文件(手机照片怎么弄成pdf)

  • 批量设置包含特定名称文件的属性(怎么批量给指定条件加入批注)

    批量设置包含特定名称文件的属性(怎么批量给指定条件加入批注)

  • Windows10 2009永久激活秘钥怎么获得 win10神key分享(win10永久激活2021)

    Windows10 2009永久激活秘钥怎么获得 win10神key分享(win10永久激活2021)

  • 增值税17-16-13的时间
  • 以不动产投资入股增值税
  • 交易性金融资产的账务处理
  • 出现哪些情形纳入医保
  • 税务筹划有哪些内容
  • 服务业收到服务业发票分录
  • 盈余公积要是提多了能转回么
  • 融资租赁首付款支付给供应商
  • 已结转的凭证可以取消结转吗
  • 收到长期股权投资的现金股利
  • 进口后的技术服务费
  • 生产企业电费要不要计提?
  • 应收票据借方表示负债吗
  • 提供建筑服务应在发票备注栏注明
  • 补偿金需要缴纳个税分开
  • 无形资产软件摊销年限是多少
  • 两年前少缴的税款是否应补缴?
  • 吸收合并税费
  • 企业的应收帐款增长率超过销售收入增长率是正常现象
  • 预转固的好处
  • 信用减值损失借方余额在利润表中
  • 公司开一般户弊端
  • 装修费用资本化的条件
  • mac怎么保存网页上的音频
  • win11自带照片查看器
  • 拍卖获得收入个税
  • thinkphp autoload 命名空间自定义 namespace
  • agsservice是什么进程
  • php autoloader
  • 流转税政策
  • 税后讲课费的个税由谁承担
  • 转让应收账款是否交税
  • mksysb命令
  • 上市公司分红派股
  • 长期股权投资采用权益法核算
  • 小规模与一般纳税人做账区别
  • 作用域和作用域链的理解
  • 税控盘登录密码设置
  • 哪一款macbook
  • 实现自己的http server loop_in_codes C++博客
  • list删除某个元素 python
  • 企业购进商品支付货款时,实际发生现金折扣,应计入
  • mongo创建db
  • 工程发票多少点
  • 展会补贴需要准备什么资料
  • 公司废料收入如何开票
  • 增值税发票日用品开票明细
  • 财政补贴收入要交增值税税率
  • 未投入使用的固定资产折旧计入什么科目
  • 业务招待费的企业所得税扣除标准
  • 应收账款核算流程
  • 企业对于无法收回的应收账款,应列入
  • 低值易耗品怎么摊
  • 进口报关费入什么科目
  • 土地出让金抵减增值税
  • 小规模纳税人开专票税率是多少?
  • 税局代开的法律顾问费能否抵扣?
  • 小规模企业能否消化13点增值税普通发票
  • 企业资质证书丢失怎么办
  • 会计利润和税务利润的差异是什么
  • 商业企业费用科目
  • win10h2版本
  • mac系统怎么共享文件夹
  • 装系统无法选择
  • linux网络设置在哪里
  • win7系统电脑怎么开热点
  • jquery判断div是否显示
  • node.js批量添加数据
  • 如何理解shell
  • easyui-accordion
  • unity摄像机范围
  • js中的三种弹出消息提醒的命令
  • 上海烟草集团董事长是谁
  • 知道税率怎么算成本
  • 税务副处级竞争上岗笔试题
  • 国税系统如何查询发票
  • 贵州省税务网上缴费平台
  • 进口酒类税收
  • 国税地税什么时候申报
  • 从哪个国家进口牛肉
  • 免责声明:网站部分图片文字素材来源于网络,如有侵权,请及时告知,我们会第一时间删除,谢谢! 邮箱:opceo@qq.com

    鄂ICP备2023003026号

    网站地图: 企业信息 工商信息 财税知识 网络常识 编程技术

    友情链接: 武汉网站建设