简介:在第五节的内容中,我们学习了使用rqt工具集观看摄像头视频流的方法,细心的同学应该会发现camera_node发布的视频数据中的图像有变形现象,图像变形会导致直线不直,部分区域变大,部分区域缩小,导致无法准确计算出我们需要的数据,为了解决这个问题,我们需要先校准相机,另外在虚拟仿真的学习过程中,我们了解到在后续的图像处理过程中,我们还需要知道相机的内参矩阵,以及投影变换矩阵,在这一章节中,我们详细介绍这些参数的求取过程。

在求取参数过程中,我们需要借助棋盘格来辅助完成相机的校准,我们使用的棋盘格大小是一张标准的A3纸的尺寸,样式如下图:

棋盘格也标示了车辆的坐标系统。x轴向前正,y轴向左为正,如果你手上没有标准的棋盘格需要自制的话,有以下几个参数需要注意:

1、棋盘格为6(行)*8(列),我们实际使用的是内角点,所以后续配置参数为5*7

2、每个棋盘格中的小方块是标准的31mm,不能缩放

3、以车辆坐标为参考,棋盘格右下角坐标为(160mm,-124mm)

未校准情况下,摄像头中的棋盘格如下图,明显存在变形,而我们需要的是横平竖直,格子大小相等的图像。

在虚拟仿真环境中我们是通过虚拟仿真环境截取了一些棋盘格图像然后计算相关参数的,在真实环境中,我们可以手动来获取这些图像,然后统一加载图像再计算相关参数,为了减少工作量,我们通过编程实现一次性完成图像采集和参数的计算,具体流程如下:


        目录

1、创建工作空间以及功能包

2、主要功能函数说明

2.1 cv2.findChessboardCorners 查找图像中棋盘格角点信息

2.2 cv2.cornerSubPix 亚像素角点检测

2.3 cv2.drawChessboardCorners 绘制标定的角点

2.4 cv2.calibrateCamera 计算相机内参系数

2.5 cv2.getOptimalNewCameraMatrix 获取视场调节参数

2.6 cv2.undistort 图像去畸变

3、编程实现图像校准,计算校准参数

4、上传校准参数


1、创建工作空间以及功能包

由于需要采集图像,我们需要在ROS平台上完成以下功能,先创建新的ROS工作空间

$ cd ~
$ mkdir -p duckietown/catkin_ws/src
$ cd duckietown/catkin_ws/src
$ catkin_init_workspace
$ cd ..
$ catkin_make

新建摄像头校准功能包,并新建源码文件

$ cd src
$ catkin_create_pkg calibrate rospy sensor_msgs
$ touch calibrate/src/calibrate_node.py

修改编译配置文件如下图:

$ gedit calibrate/CMakeLists.txt


2、主要功能函数说明

2.1 cv2.findChessboardCorners 查找图像中棋盘格角点信息

retval, corners = cv.findChessboardCorners(image, patternSize[, corners[, flags]])

第一个参数Image,传入拍摄的棋盘图Mat图像,必须是8位的灰度或者彩色图像;

第二个参数patternSize,每个棋盘图上内角点的行列数,一般情况下,行列数不要相同,便于后续标定程序识别标定板的方向;

第三个参数corners,用于存储检测到的内角点图像坐标位置,一般是数组形式;

第四个参数flage:用于定义棋盘图上内角点查找的不同处理方式,有默认值。

返回值corners中包含了检测到的角点信息

2.2 cv2.cornerSubPix 亚像素角点检测

为了提高标定精度,需要在初步提取的角点信息上进一步提取亚像素信息,降低相机标定偏差,常用的方法是cornerSubPix函数:

cv2.cornerSubPix(image, corners, winSize, zeroZone, criteria)

第一个参数image,输入图像的像素矩阵,最好是8位灰度图像,检测效率更高;

第二个参数corners,初始的角点坐标向量,同时作为亚像素坐标位置的输出,所以需要是浮点型数据;

第三个参数winSize,大小为搜索窗口的一半;

第四个参数zeroZone,死区的一半尺寸,死区为不对搜索区的中央位置做求和运算的区域。它是用来避免自相关矩阵出现某些可能的奇异性。当值为(-1,-1)时表示没有死区;

第五个参数criteria,定义求角点的迭代过程的终止条件,可以为迭代次数和角点精度两者的组合;

2.3 cv2.drawChessboardCorners 绘制标定的角点

找到角点信息后,我们可以用 drawChessboardCorners函数用绘制被成功标定的角点:

cv2.drawChessboardCorners(image, patternSize, corners, patternWasFound)

第一个参数image,8位灰度或者彩色图像;

第二个参数patternSize,每张标定棋盘上内角点的行列数;

第三个参数corners,初始的角点坐标向量,同时作为亚像素坐标位置的输出,所以需要是浮点型数据;

第四个参数patternWasFound,标志位,用来指示定义的棋盘内角点是否被完整的探测到,true表示别完整的探测到,函数会用直线依次连接所有的内角点,作为一个整体,false表示有未被探测到的内角点,这时候函数会以(红色)圆圈标记处检测到的内角点;

2.4 cv2.calibrateCamera 计算相机内参系数

获取到棋盘标定图的内角点图像坐标之后,就可以使用calibrateCamera函数进行标定,计算相机内参系数:

retval, cameraMatrix, distCoeffs, rvecs, tvecs = cv2.calibrateCamera(objectPoints, imagePoints, imageSize, cameraMatrix, distCoeffs[, rvecs[, tvecs[, flags[, criteria]]]])

第一个参数objectPoints,为世界坐标系中的三维点。需要依据棋盘上单个黑白矩阵的大小,计算出(初始化)每一个内角点的世界坐标;

第二个参数imagePoints,为每一个内角点对应的图像坐标点;

第三个参数imageSize,为图像的像素尺寸大小,在计算相机的内参和畸变矩阵时需要使用到该参数;

第四个参数cameraMatrix为相机的内参矩阵;

第五个参数distCoeffs为畸变矩阵;

第六个参数rvecs为旋转向量;

第七个参数tvecs为位移向量;

第八个参数flags为标定时所采用的算法。有如下几个参数:

CV_CALIB_USE_INTRINSIC_GUESS:使用该参数时,在cameraMatrix矩阵中应该有fx,fy,u0,v0的估计值。否则的话,将初始化(u0,v0)图像的中心点,使用最小二乘估算出fx,fy。

CV_CALIB_FIX_PRINCIPAL_POINT:在进行优化时会固定光轴点。当CV_CALIB_USE_INTRINSIC_GUESS参数被设置,光轴点将保持在中心或者某个输入的值。

CV_CALIB_FIX_ASPECT_RATIO:固定fx/fy的比值,只将fy作为可变量,进行优化计算。当CV_CALIB_USE_INTRINSIC_GUESS没有被设置,fx和fy将会被忽略。只有fx/fy的比值在计算中会被用到。

CV_CALIB_ZERO_TANGENT_DIST:设定切向畸变参数(p1,p2)为零。

CV_CALIB_FIX_K1,…,CV_CALIB_FIX_K6:对应的径向畸变在优化中保持不变。

CV_CALIB_RATIONAL_MODEL:计算k4,k5,k6三个畸变参数。如果没有设置,则只计算其它5个畸变参数。

第九个参数criteria是最优迭代终止条件设定。

返回值cameraMatrix:相机内参矩阵

返回值distCoeffs:相机径向畸变参数

2.5 cv2.getOptimalNewCameraMatrix 获取视场调节参数

优化内参数和畸变系数,通过设定自由比例因子alpha。当alpha设为0的时候,将会返回一个剪裁过的将去畸变后不想要的像素去掉的内参数和畸变系数;当alpha设为1的时候,将会返回一个包含额外黑色像素点的内参数和畸变系数,并返回一个ROI用于将其剪裁掉。

newcameramtx, roi=cv2.getOptimalNewCameraMatrix(mtx,dist,(w,h),alpha,(w,h))

参数1:mtx,相机内参矩阵

参数2:dist,相机径向畸变参数

参数3:(w,h)原图像尺寸

参数4:alpha 视场大小调节,0放大,1不变

参数5:(w,h)新图像大小

返回值newcameramtx:视场调节参数矩阵

返回值roi:感兴趣区域

2.6 cv2.undistort 图像去畸变

得到去畸变的图像,使用上面的ROI可以对其进行剪裁

dst = cv2.undistort(img, mtx, dist, None, newcameramtx)

参数1:img 原图

参数2:mtx 相机内参矩阵

参数3:dist 相机径向畸变参数

参数4:None

参数5:newcameramtx 视场调节参数矩阵

返回值dst 去畸变的图像


3、编程实现图像校准,计算校准参数

$ gedit calibrate/src/calibrate_node.py
#!/usr/bin/env python3
#encoding=utf-8import rospy
import cv2
import math
import numpy as np
from PIL import Image,ImageDraw,ImageFontfrom sensor_msgs.msg import CompressedImage
from cv_bridge import CvBridgeclass CalibrateNode():def __init__(self):rospy.init_node("calibrate_node",anonymous=False)       self.bridge = CvBridge()self.nx = 5 #棋盘格内角点行数self.ny = 7 #棋盘格内角点列数#世界坐标系中的三维点self.objp = np.zeros((self.nx*self.ny,3), np.float32)self.objp[:,:2] = np.mgrid[0:self.nx,0:self.ny].T.reshape(-1,2)self.objpoints = [] #三维点列表self.imgpoints = [] #二维点列表#提示信息self.notes = ['0.请将棋盘纸放入摄像头视野中','1.请将棋盘格置于摄像头视野中间,面积占整体画面1/5左右','2.请将棋盘格向前移动,尽量占满摄像头视野','3.请将棋盘格向后移动,直到面积占整体画面1/20左右','4.请将棋盘格置于摄像头视野中间,并向上移动至视野上边缘','5.请将棋盘格置于摄像头视野中间,并向下移动至视野下边缘','6.请将棋盘格置于摄像头视野中间,并向右移动至视野左边缘','7.请将棋盘格置于摄像头视野中间,并向左移动至视野右边缘','8.请将棋盘格置于摄像头视野中间,并移动至视野左上边缘','9.请将棋盘格置于摄像头视野中间,并移动至视野左下边缘','10.请将棋盘格置于摄像头视野中间,并移动至视野右上边缘','11.请将棋盘格置于摄像头视野中间,并移动至视野右下边缘','12.请将棋盘格置于摄像头视野中间,并向前倾斜60度角','13.请将棋盘格置于摄像头视野中间,并向后倾斜60度角','14.请将棋盘格置于摄像头视野中间,并向左倾斜60度角','15.请将棋盘格置于摄像头视野中间,并向右倾斜60度角','16.数据采集完毕,开始校准摄像头','17.校准完成,请保存校准数据']self.step = 0  #操作步骤计数self.mtx = None  #内参矩阵self.dist = None  #径向畸变矩阵self.newcameramtx = None #感兴趣区域(ROI)截取矩阵self.roi = None #感兴趣区域#求角点的迭代过程的终止条件,采用的停止准则是最大循环次数31和最大误差容限0.001self.criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 31, 0.001)self.skip = False  #为减少卡顿,设定图片跳帧标志self.count = 0  #图片跳帧计数#订阅小车图像话题,需要根据小车主机名做相应修改rospy.Subscriber("/duckiebot1/camera_node/image/compressed", CompressedImage, self.cb_image)def cb_image(self, msg):if self.skip or self.count<4: #设定跳帧条件,不符合则跳过当前帧self.skip = Falseself.count += 1returnelse:self.count = 0self.skip = True#图片消息数据转化为opencv2图片格式image = self.bridge.compressed_imgmsg_to_cv2(msg)if self.step<16:  #操作步骤前16步工作内容是数据采集image = self.find_corners(image)elif self.step==16: #第17步根据前16步采集的数据计算摄像头各项参数shape = image.shaperet, self.mtx, self.dist, rvecs, tvecs = cv2.calibrateCamera(self.objpoints, self.imgpoints, (shape[0],shape[1]), None, None)self.newcameramtx, self.roi = cv2.getOptimalNewCameraMatrix(self.mtx,self.dist,(shape[1],shape[0]),0,(shape[1],shape[0]))self.step += 1print("mtx:", self.mtx)print("dist:", self.dist)elif self.step==17: #利用计算出的校准参数校准图片image = cv2.undistort(image, self.mtx, self.dist, None, self.newcameramtx)#为图片添加提示文字信息image = self.cv2ImgAddText(image, self.notes[self.step], 10, 10, (255, 0, 0), 20)cv2.imshow("image",image)cv2.waitKey(1)#利用cv2.findChessboardCorners和cv2.cornerSubPix函数查找棋盘格内角点信息,并绘制到图片上def find_corners(self, image):#转化灰度图片gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)#查找内角点信息ret, corners = cv2.findChessboardCorners(gray, (self.nx, self.ny), None)if ret==True:#查找亚像素角点信息cv2.cornerSubPix(gray,corners,(11,11),(-1,-1),self.criteria)#绘制角点图像return self.draw_conners(image, corners)else: #如果没有查找到角点信息,则下一帧图片跳过不处理self.skip = Truereturn image#绘制角点数据,根据角点信息判断棋盘格位置,符合筛选条件的位置保存相应角点信息def draw_conners(self, image, corners):shape = image.shapecns = np.zeros((7,5,2),np.float32) #新建数组存储角点坐标for idx, cn in enumerate(corners):[[x,y]] = cncns[idx//5][idx%5] = [x, y]#第0步检测到棋盘格即可if self.step==0:self.step += 1#第1和3步需要棋盘格距离相机较近和较远,近需要占据总图片面积15%以上区域,远需要占据图片面积5%以下elif self.step==1 or self.step==3:w = cns[6][4][0]-cns[0][0][0]h = cns[6][4][1]-cns[0][0][1]if self.step==1 and w*h/(shape[0]*shape[1])>=0.15:             self.saveCornersData(corners)elif self.step==3 and w*h/(shape[0]*shape[1])<=0.05:            self.saveCornersData(corners)#第2步棋盘格尽量占满摄像头视野,第4~11步需要棋盘格分别出现在相机视野的上、下、左、右、左上、左下、右上、右下。elif self.step==2 or (self.step>=4 and self.step<=11):margin_top = np.sum(cns[0], 0)[1]/5  #棋盘格距离上边缘平均距离margin_bottom = shape[0] - np.sum(cns[6], 0)[1]/5 #棋盘格距离下边缘平均距离margin_left = np.sum(cns[:,:1,:],0)[0][0]/7  #棋盘格距离左边缘平均距离margin_right = shape[1] - np.sum(cns[:,4:,:],0)[0][0]/7  #棋盘格距离右边缘平均距离#第2步、上下接近边缘或者左右接近边缘表示棋盘格占满图像if self.step==2 and ((margin_top<shape[0]/8 and margin_bottom<shape[0]/8) or (margin_left<shape[1]/8 and margin_right<shape[1]/8)):self.saveCornersData(corners)#第4步、棋盘格接近上边缘elif self.step==4 and margin_top<shape[0]/8:self.saveCornersData(corners)#第5步、棋盘格接近下边缘elif self.step==5 and margin_bottom<shape[0]/8:self.saveCornersData(corners)#第6步、棋盘格接近左边缘elif self.step==6 and margin_left<shape[1]/8:self.saveCornersData(corners)#第7步、棋盘格接近右边缘elif self.step==7 and margin_right<shape[1]/8:self.saveCornersData(corners)#第8步、棋盘格接近左上边缘elif self.step==8 and margin_top<shape[0]/8 and margin_left<shape[1]/8:self.saveCornersData(corners)#第9步、棋盘格接近左下边缘elif self.step==9 and margin_bottom<shape[0]/8 and margin_left<shape[1]/8:self.saveCornersData(corners)#第10步、棋盘格接近右上边缘elif self.step==10 and margin_top<shape[0]/8 and margin_right<shape[1]/8:self.saveCornersData(corners)#第11步、棋盘格接近右下边缘elif self.step==11 and margin_bottom<shape[0]/8 and margin_right<shape[1]/8:self.saveCornersData(corners)#第12-15步,需要棋盘格在图像中做出倾斜状态,当一个正方形被倾斜后,其宽高比会发生变化elif self.step>=12 and self.step<=15:w_top = cns[0][4][0]-cns[0][0][0] #棋盘格顶部宽度w_bottom = cns[6][4][0]-cns[6][0][0] #棋盘格底部宽度h_left = cns[6][0][1] - cns[0][0][1] #棋盘格左侧宽度h_right = cns[6][4][1] - cns[0][4][1] #棋盘格右侧宽度w_mean = (w_top + w_bottom)/8 #计算横向方格平均宽度h_mean = (h_left +h_right)/12 #计算纵向方格平均高度#第12步、前倾if self.step==12 and w_top>w_bottom and w_mean/h_mean>=2:self.saveCornersData(corners)#第13步、后倾elif self.step==13 and w_top<w_bottom and w_mean/h_mean>=2:self.saveCornersData(corners)#第14步、左倾elif self.step==14 and h_left>h_right and h_mean/w_mean>=2:self.saveCornersData(corners)#第15步、右倾elif self.step==15 and h_left<h_right and h_mean/w_mean>=2:self.saveCornersData(corners)#内角点信息画到图片上cv2.drawChessboardCorners(image, (self.nx, self.ny), corners, True)return image#为图片附加提示文字def cv2ImgAddText(self, img, text, left, top, textColor, textSize):#图像转化为PLT格式img = Image.fromarray(cv2.cvtColor(img,cv2.COLOR_BGR2RGB))draw = ImageDraw.Draw(img)#设置字体格式ttf = "/usr/share/fonts/opentype/noto/NotoSansCJK-Bold.ttc"fontStyle = ImageFont.truetype(ttf, textSize, encoding="utf-8")#将文字附加到图片上draw.text((left,top),text,textColor,font=fontStyle)#图片再转化回opencv格式return cv2.cvtColor(np.asarray(img),cv2.COLOR_RGB2BGR)#存储内角点信息def saveCornersData(self, corners):self.objpoints.append(self.objp)self.imgpoints.append(corners)self.step += 1 if __name__=='__main__':node = CalibrateNode()rospy.spin()

编译源码

$ cd ~/duckietown/catkin_ws
$ catkin_make

设置环境变量

$ source devel/setup.bash

运行程序

$ rosrun calibrate calibrate_node.py

按照提示依次操作棋盘格做出指定动作

最后在终端打印相机内参以及径向畸变参数:

mtx: [[306.09044878 0. 328.29132065] [ 0. 304.98753442 235.57672176] [ 0. 0. 1. ]]

dist: [[-3.11854407e-01 9.77819171e-02 1.91544813e-03 1.56072741e-04 -1.38483714e-02]]

计算出相机矩阵和径向畸变参数后,对图像进行校准,下图可见棋盘格已经校正,所有格子都恢复为正方形。

注:相机校准过程中,需要保证棋盘格平整,所以棋盘格需要附着到一个刚性的物体上,如果棋盘格有变形情况,那么校准参数就会失真,会影响后续车道线识别准确度。

4、上传校准参数

相机校准参数是个性化参数,每辆车都会不相同,所以需要上传到小车上保存,操作步骤如下:

远程登录小车:

$ ssh duckie@duckiebot1.local
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
duckie@duckiebot1.local's password: quackquack

编辑相机参数配置文件:

$ vim /data/config/calibrations/camera_intrinsic/default.yaml

修改其中的camera_matrix和distortion_coefficients为我们计算出的参数

image_width: 640
image_height: 480
camera_name: /porsche911/rosberrypi_cam
camera_matrix:rows: 3cols: 3data:- 306.09044878- 0- 328.29132065- 0- 304.98753442- 235.57672176- 0- 0- 1
distortion_model: plumb_bob
distortion_coefficients:rows: 1cols: 5data:- -3.11854407e-01- 9.77819171e-02- 1.91544813e-03- 1.56072741e-04- -1.38483714e-02
rectification_matrix:rows: 3cols: 3data:- 1- 0- 0- 0- 1- 0- 0- 0- 1
projection_matrix:rows: 3cols: 4data:- 210.1107940673828- 0- 327.2577820024981- 0- 0- 253.8408660888672- 239.9969353923052- 0- 0- 0- 1- 0

vim基本操作命令简介:打开后按i键进入编辑模式,按方向键操作光标移动,编辑后,按esc退出编辑模式,输入:wq保存并退出,输入:q!不保存退出。

重启小车

$ sudo reboot

等待小车重启完成后,在虚拟机查看小车摄像头参数信息:

$ rostopic echo /duckiebot1/camera_node/camera_info

其中的K和D就是我们计算出的参数,在后续图像处理中需要根据这两个参数对图像进行校准后再识别。

无人驾驶小车调试笔记(七)-- 相机校准相关推荐

  1. 无人驾驶小车调试笔记(五)-- 命令行通信

            简介:在这一章节中,我们需要安装有相关环境(虚拟机方式为例,需要安装有ROS系统,安装过程请参考无人驾驶虚拟仿真教程第2节)的电脑一台,组装好并经过初始化设置的无人驾驶小车一台,路由器 ...

  2. 无人驾驶小车调试笔记(三)--小车启动及初始化设置

            准备工作:安装有相关环境的电脑一台,组装好的无人驾驶小车一台,烧录好系统的SD卡一张,与烧录SD卡配置一致的路由器一台. 注:以下内容都以主机名为duckiebot1的小车为例说明. ...

  3. 无人驾驶小车调试笔记(六)-- 车轮校准

    简介:小车的动力完全来自于两个电机带动的车轮,在理想状态下,给两个电机同样的驱动参数,两个车轮会以同样的转速带动小车直线行驶,而实际情况是每个电机可能都会有个体差异,也就是说给予同样的驱动参数,表现出 ...

  4. 无人驾驶小车调试笔记(一)-- 调试环境部署

    硬件简介:硬件是NVIDA Jetson Nano为主控的差速控制小车,主要由主控板.转接板.两个电机(动力来源).高清摄像头.tof传感器.imu传感器.前后各两组车灯,顶部开关按钮.显示屏以及万向 ...

  5. 哇靠靠,这也行?零基础DIY无人驾驶小车(一)

    之前做过一个非常简单粗略版本的无人驾驶小车(点击打开链接).曾经答应过一些朋友出教程的,可是一直没有兑现.现在下定决心要把这个教程写完.而且会重新制作一辆更好的无人驾驶小车(使用标准的4车道,增加其他 ...

  6. ROS学习笔记七:使用rqt_console和roslaunch

    ROS学习笔记七:使用rqt_console和roslaunch 本节主要介绍在调试时使用的rqt_console和rqt_logger_level,以及一次性打开多个节点的工具roslaunch. ...

  7. RISC-V IDE MRS使用笔记(七) :常用开发技巧汇总

    RISC-V IDE MRS使用笔记(七) :常用开发技巧汇总 Q1: MRS调试时如何查看外设寄存器内容? A1: 在调试配置界面添加相应的.svd文件. Q2: MRS如何调用数学库? A2: # ...

  8. ARM+Movidius VPU 目标识别调试笔记(一)

    写在前面 Movidius 是一家被Intel收购的计算机视觉创业公司.Intel收购Movidius之后,在AI和边缘计算方面的布局又增加了一支生力军.Movidius的AI芯片分为Myriad x ...

  9. STM32学习笔记(七)---SysTick

    STM32学习笔记(七)-SysTick 文章目录 STM32学习笔记(七)---SysTick 一.SysTick简介 二.SysTick功能框图 三.SysTick寄存器 四.SYSTICK使用 ...

最新文章

  1. Visual Studio视觉编程工具(推荐四个)
  2. oracle只修改年份
  3. 4个重要的量子理论实验综述
  4. 每日一皮:即使是个小BUG,有时候也能吓你半条命!
  5. MySQL双主(主主)架构方案
  6. python读写xlsx文件_python读写Excel文件--使用xlrd模块读取,xlwt模块写入
  7. Android Studio Gradle两种更新方式
  8. 【Linux】在Deepin v20或UOS20下运行MC我的世界
  9. Ubuntu14.04安装VMwareTools
  10. 拓端tecdat|R语言向量自回归模型(VAR)及其实现
  11. 《实施Cisco统一通信管理器(CIPT1)》一1.3 总结
  12. 基于springboot的在线考试系统
  13. 基础知识:计算机网络--《趣谈网络协议》读书笔记
  14. 零基础学习用labview制作温度采集系统
  15. 手机屏幕分类、材料硬度解析
  16. 1000道最新高频Java面试题,覆盖25个技术栈(多线程、JVM、高并发、spring、微服务、kafka,redis、分布式)从底层原理到架构!
  17. 用python打印出一个人的照片_python爬取图片实例 - 一个人看日出,一个人看夕阳,一个人走路,一个人欢笑 - OSCHINA - 中文开源技术交流社区...
  18. 用instsrv.exe+srvany.exe将应用程序安装为windows服务
  19. 格林耐特交换机配置命令参考
  20. 微信小程序之Data当前日期坑 安卓 ios日期不对

热门文章

  1. Win7 的安全快捷键使用技巧
  2. 数字从后往前每三位加个逗号
  3. WordPress搭建过程
  4. C#切换鼠标左右键习惯
  5. 微信小程序富文本渲染(rich-text)换行失效
  6. 家用智能投影推荐 五千元档的当贝F5和当贝X3有哪些区别?
  7. 固定于计算机主机箱中承载,计算机主机完整
  8. unity3d序列帧动画无法显示坑
  9. 用python3写一个小球转动的抽奖小游戏
  10. 吴恩达深度学习编程题——0402(Keras-Tutorial-Happy House )遇到问题及解决方法