diff --git a/README.md b/README.md index b405cf57..d1906429 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,5 @@ -# exercise_2 +3rdparty为第三方库; +doc放相关文档 +src源代码 +moderl为uml图 diff --git a/doc/~$沙盘软件设计规格说明书.doc b/doc/~$沙盘软件设计规格说明书.doc index 33de3025..03e6748d 100644 Binary files a/doc/~$沙盘软件设计规格说明书.doc and b/doc/~$沙盘软件设计规格说明书.doc differ diff --git a/doc/虚拟沙盘系统用户界面设计.doc b/doc/虚拟沙盘系统用户界面设计.doc deleted file mode 100644 index a9a77e3e..00000000 Binary files a/doc/虚拟沙盘系统用户界面设计.doc and /dev/null differ diff --git a/doc/虚拟沙盘软件设计规格说明书.doc b/doc/虚拟沙盘软件设计规格说明书.doc index bdb69352..205f5f98 100644 Binary files a/doc/虚拟沙盘软件设计规格说明书.doc and b/doc/虚拟沙盘软件设计规格说明书.doc differ diff --git a/src/calib_RGB.py b/src/SanDCJ/calib_RGB.py similarity index 100% rename from src/calib_RGB.py rename to src/SanDCJ/calib_RGB.py diff --git a/src/SanDCJ/huidu_shuai.py b/src/SanDCJ/huidu_shuai.py new file mode 100644 index 00000000..4db402a0 --- /dev/null +++ b/src/SanDCJ/huidu_shuai.py @@ -0,0 +1,46 @@ +import PIL.Image +import numpy +import os +import shutil +def sum_right(path): + img = PIL.Image.open(path) + array = numpy.array(img) + num = array.sum(axis=0) + print(type(num)) + res_left = 0 + res_right = 0 + for i in range(256,512): + res_right += num[i] + print(res_right) + +# if __name__ == '__main__': +# dir2 = r"C:\Users\xinluo\PycharmProjects\pythonProject\Camera Roll" +# dir1 = r"C:\Users\xinluo\PycharmProjects\pythonProject\Saved Pictures" +# names = os.listdir(dir1) +# n = len(names) +# print("文件数量",n) +# res = 0 +# average_5 = 25565356 +# average_25 = 26409377 +# average_5_right = 10006019 +# #average_tmp = (average_25+average_5)//2 +# count = 0 +# #show(os.path.join(dir1, "uni4F6C.png")) +# for i in range(n): +# #取图片 +# img = PIL.Image.open(os.path.join(dir1,names[i])) +# file = os.path.join(dir1,names[i]) +# rmfile = os.path.join(dir2,names[i]) +# array = numpy.array(img) +# num = array.sum(axis=0) +# res_right = 0 +# for i in range(256, 512): +# res_right += num[i] +# average_5_right += res_right/n +# +# if (res_right > average_5_right).all(): +# shutil.copyfile(file, rmfile) +# os.remove(file) +# count += 1 +# print(average_5_right) +# print(count) diff --git a/src/SanDCJ/qingxi_shuai.py b/src/SanDCJ/qingxi_shuai.py new file mode 100644 index 00000000..6b38de3c --- /dev/null +++ b/src/SanDCJ/qingxi_shuai.py @@ -0,0 +1,27 @@ +# -*- coding: UTF-8 -*- + +import argparse +import cv2 +import os + +# 使用方法:命令行cd到脚本所在文件夹,python picture.py -i "图片路径" +#便历图片,合格则保存,不合格则删除 + + +def variance_of_laplacian(image): + # 使用拉普拉斯算子提取图像边缘信息,计算边缘的平均方差 + return cv2.Laplacian(image, cv2.CV_64F).var() +def read_path(file_pathname): + #遍历该目录下的所有图片文件 + for filename in os.listdir(file_pathname): + print(filename) + image = cv2.imread(file_pathname+'/'+filename) + # 取图片的灰度通道 + gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) + fm = variance_of_laplacian(gray) + + # 如果得分比阈值低,认为是模糊 + if fm < 100.0: + os.remove(file_pathname+'/'+filename) +#read_path(r"C:\Users\xinluo\PycharmProjects\pythonProject\Saved Pictures") +#print(os.getcwd()) diff --git a/src/SanDCJ/video.py b/src/SanDCJ/video.py new file mode 100644 index 00000000..269d9e64 --- /dev/null +++ b/src/SanDCJ/video.py @@ -0,0 +1,274 @@ +import logging +import os +import shutil + +import PIL +from PyQt5 import QtCore, QtGui, QtWidgets +import numpy as np +import matplotlib +import sys +import cv2 +from djitellopy import tello +import matplotlib.pyplot as plt +# matplotlib.figure 模块提供了顶层的Artist(图中的所有可见元素都是Artist的子类),它包含了所有的plot元素 +from matplotlib.figure import Figure +from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg # pyqt5的画布 +import calib_RGB +import huidu_shuai,qingxi_shuai,xiangsu_shuai,video_to_photo +import argparse + +class MyMatplotlibFigure(FigureCanvasQTAgg): + """ + 创建一个画布类,并把画布放到FigureCanvasQTAgg + """ + def __init__(self, width=10, heigh=10, dpi=100): + plt.rcParams['figure.facecolor'] = 'r' # 设置窗体颜色 + plt.rcParams['axes.facecolor'] = 'b' # 设置绘图区颜色 + self.width_ = width + self.heigh_ = heigh + self.dpi = dpi + self.figs = Figure(figsize=(self.width_, self.heigh_), dpi=self.dpi) + super(MyMatplotlibFigure, self).__init__(self.figs) # 在父类种激活self.fig, 否则不能显示图像 + #self.axes = self.figs.add_subplot(111,projection='3d') + fig = plt.figure() + #fig.suptitle('3D reconstructed', fontsize=16) + #self.axes = fig.gca(projection='3d') + self.axes=fig.add_subplot(111, projection='3d') + + def mat_plot_drow_axes(self, t, s, l): + + """ + 用清除画布刷新的方法绘图 + :return: + """ + self.axes.cla() # 清除绘图区 + self.axes.spines['top'].set_visible(False) # 顶边界不可见 + self.axes.spines['right'].set_visible(False) # 右边界不可见 + # fig = plt.figure() + # fig.suptitle('3D reconstructed', fontsize=16) + # ax = plt.axes(projection='3d') + # 设置左、下边界在(0,0)处相交 + # self.axes.spines['bottom'].set_position(('data', 0)) # 设置y轴线原点数据为 0 + self.axes.spines['left'].set_position(('data', 0)) # 设置x轴线原点数据为 0 + # self.axes.plot(t, s, l, 'b.') + # self.set_xlabel('x axis') + # self.set_ylabel('y axis') + # self.set_zlabel('z axis') + # self.view_init(elev=135, azim=90) + # plt.savefig('Reconstruction.jpg') + # plt.show() + # self.axes.plot(t, s, l, 'o-r', linewidth=0.5) + self.axes.scatter(t,s,l, 'b.') + #self.axes.plot_surface(t,s,l) + self.axes.set_xlabel('x axis') + self.axes.set_ylabel('y axis') + self.axes.set_zlabel('z axis') + self.axes.view_init(elev=135, azim=90) + plt.savefig('Reconstruction.jpg') + plt.show() + self.figs.canvas.draw() # 这里注意是画布重绘,self.figs.canvas + self.figs.canvas.flush_events() # 画布刷新self.figs.canvas + + + + +class Ui_MainWindow(QtWidgets.QWidget): + def __init__(self, parent=None): + super().__init__(parent) + self.timer_camera = QtCore.QTimer() + self.cap = cv2.VideoCapture() + self.CAM_NUM = 0 + + self.set_ui() + self.slot_init() + + + def set_ui(self): + + self.setWindowTitle('虚拟沙盘') # 标题 + + self.resize(1200, 800) # 窗口尺寸 + + #self.status = self.statusTip('虚拟沙盘3D建模系统') # 消息 + + #self.status.showMessage() + + self.__layout_main = QtWidgets.QVBoxLayout() + self.__layout_fun_button = QtWidgets.QHBoxLayout() + self.__layout_data_show = QtWidgets.QHBoxLayout() + self.button_open_camera = QtWidgets.QPushButton('打开摄像头') + self.button_operate = QtWidgets.QPushButton('键盘控制') + self.button_takephoto = QtWidgets.QPushButton('视频转图片') + self.button_make = QtWidgets.QPushButton('建模') + self.button_close = QtWidgets.QPushButton('退出') + + self.button_open_camera.setMinimumHeight(50) + self.button_close.setMinimumHeight(50) + self.button_operate.setMinimumHeight(50) + self.button_takephoto.setMinimumHeight(50) + self.button_make.setMinimumHeight(50) + self.button_open_camera.move(10, 100) + self.label_show_camera = QtWidgets.QLabel() + #self.label_show_camera.move(400,400) + self.label_show_camera.setFixedSize(841, 681) + self.__layout_fun_button.addWidget(self.button_open_camera) + self.__layout_fun_button.addWidget(self.button_operate) + self.__layout_fun_button.addWidget(self.button_takephoto) + self.__layout_fun_button.addWidget(self.button_make) + self.__layout_fun_button.addWidget(self.button_close) + self.__layout_main.addLayout(self.__layout_fun_button) + self.__layout_main.addWidget(self.label_show_camera) + self.setLayout(self.__layout_main) +########################################## + + + + # 将信号与槽关联 + #self.btn1.clicked.connect(self.onClick_Button) + #self.btn2.clicked.connect(self.showDialog) + def slot_init(self): + self.button_open_camera.clicked.connect( + self.button_open_camera_clicked) + self.timer_camera.timeout.connect(self.show_camera) + self.button_close.clicked.connect(self.onClick_Button) + self.button_make.clicked.connect(self.showDialog) + self.button_takephoto.clicked.connect(self.take_photo) + + + def take_photo(self): + #video to photo + args = video_to_photo.parse_args() + if not os.path.exists(args.output): + os.makedirs(args.output) + print('Called with args:') + print(args) + video_to_photo.process_video(args.input, args.output, args.skip_frame) + + #灰度值筛选 + dir2 = r"C:\Users\xinluo\PycharmProjects\pythonProject\Camera Roll" + dir1 = r"C:\Users\xinluo\PycharmProjects\pythonProject\Saved Pictures" + names = os.listdir(dir1) + n = len(names) + print("文件数量", n) + res = 0 + average_5 = 25565356 + average_25 = 26409377 + average_5_right = 10006019 + # average_tmp = (average_25+average_5)//2 + count = 0 + # show(os.path.join(dir1, "uni4F6C.png")) + for i in range(n): + # 取图片 + print(1234) + img = PIL.Image.open(os.path.join(dir1, names[i])) + file = os.path.join(dir1, names[i]) + rmfile = os.path.join(dir2, names[i]) + array = np.array(img) + num = array.sum(axis=0) + res_right = 0 + for i in range(256, 512): + res_right += num[i] + average_5_right += res_right / n +#res_right > average_5_right + if (1): + print(4321) + shutil.copyfile(file, rmfile) + os.remove(file) + count += 1 + print(average_5_right) + print(count) + #像素筛选 + xiangsu_shuai.pixel() + #清晰度筛选 + qingxi_shuai.read_path(r"C:\Users\xinluo\PycharmProjects\pythonProject\Camera Roll") + + def button_open_camera_clicked(self): + if self.timer_camera.isActive() == False: + flag = self.cap.open(self.CAM_NUM) + if flag == False: + msg = QtWidgets.QMessageBox.warning(self, 'warning', "请检查无人机是否连接正确", buttons=QtWidgets.QMessageBox.Ok) + else: + self.timer_camera.start(30) + self.button_open_camera.setText('关闭摄像头') + else: + self.timer_camera.stop() + self.cap.release() + self.label_show_camera.clear() + self.button_open_camera.setText('打开摄像头') + + def show_camera(self): + self.Drone = tello.Tello() # 创建飞行器对象 + self.Drone.connect() # 连接到飞行器 + + self.Drone.streamon() # 开启视频传输 + self.Drone.LOGGER.setLevel(logging.ERROR) # 只显示错误信息 + #sleep(5) # 等待视频初始化 + #kp.init() # 初始化按键处理模块 + flag,OriginalImage = self.Drone.get_frame_read() + #flag, self.image = self.cap.read() + show = cv2.resize(OriginalImage, (640, 480)) + show = cv2.cvtColor(show, cv2.COLOR_BGR2RGB) + showImage = QtGui.QImage(show.data, show.shape[1], show.shape[0], + QtGui.QImage.Format_RGB888) + self.label_show_camera.setPixmap(QtGui.QPixmap.fromImage(showImage)) + + #getKeyboardInput(drone=Drone, speed=70, image=Image) # 按键控制 + cv2.imshow("Drone Control Centre", OriginalImage) + def showDialog(self): + dialog = QtWidgets.QDialog() + dialog.resize(1800, 1600) + button = QtWidgets.QPushButton('储存', dialog) + button.clicked.connect(dialog.close) + button.move(1350, 750) + dialog.setWindowTitle('建模视图') + dialog.setWindowModality(QtCore.Qt.ApplicationModal) + + dialog.label = QtWidgets.QLabel(dialog) + dialog.label.setGeometry(QtCore.QRect(0, 0, 800, 600)) + # 实例化自定义画布类 + dialog.canvas = MyMatplotlibFigure(width=5, heigh=4, dpi=100) + #dialog.plotcos() + # t = np.arange(0.0, 5.0, 0.01) + # s = np.cos(2 * np.pi * t) + # dialog.canvas.mat_plot_drow_axes(t, s) + # dialog.canvas.figs.suptitle("sin") # 设置标题 + # 重建3D点 + dialog.canvas.mat_plot_drow_axes(Points3D[0], Points3D[1], Points3D[2]) + dialog.canvas.figs.suptitle("SanDCJ") # 设置标题 + + dialog.hboxlayout = QtWidgets.QHBoxLayout(dialog.label) + dialog.hboxlayout.addWidget(dialog.canvas) + #不展示dialog,只看figure + #dialog.exec() + + + + def onClick_Button(self): + sender = self.sender() #获得button + #print(sender.text() + '按钮被按下') + app = QtWidgets.QApplication.instance() + #退出程序 + app.quit() + +if __name__ == '__main__': + inter_corner_shape = (9, 6) + size_per_grid = 0.023 + # 储存用来标定相机的照片,这里是为畸变的照片 + img_dir = ".\\pic\\RGB_camera_calib_img_1" + img_type = "jpg" + # 储存标定相机之后的参数,应该相机的内外参数与畸变参数 + CameraParam_Path = '.\\CameraParam.txt' + # 要重建的目标图片 + Images_Path = 'SubstitutionCalibration_Image_1/*.jpg' + # 计算相机参数 + calib_RGB.calib(inter_corner_shape, size_per_grid, img_dir, img_type, CameraParam_Path) + # 读取相机参数 + config = calib_RGB.readfromfile(CameraParam_Path) + K = np.array(config['mtx']) + # 计算3D点 + Points3D = calib_RGB.epipolar_geometric(Images_Path, K) + + app = QtWidgets.QApplication(sys.argv) + ui = Ui_MainWindow() + ui.show() + sys.exit(app.exec_()) \ No newline at end of file diff --git a/src/SanDCJ/video_to_photo.py b/src/SanDCJ/video_to_photo.py new file mode 100644 index 00000000..ce2e686c --- /dev/null +++ b/src/SanDCJ/video_to_photo.py @@ -0,0 +1,50 @@ +import cv2 +import argparse +import os + + +def parse_args(): + """ + Parse input arguments + """ + parser = argparse.ArgumentParser(description='Process pic') + parser.add_argument('--input', help='video to process', dest='input', default=None, type=str) + parser.add_argument('--output', help='pic to store', dest='output', default=None, type=str) + # default为间隔多少帧截取一张图片 + parser.add_argument('--skip_frame', dest='skip_frame', help='skip number of video', default=10, type=int) + # input为输入视频的路径 ,output为输出存放图片的路径 + args = parser.parse_args(['--input', r'C:\Users\xinluo\PycharmProjects\pythonProject\1234.mp4', '--output', r'C:\Users\xinluo\PycharmProjects\pythonProject\Saved Pictures']) + return args + + +def process_video(i_video, o_video, num): + cap = cv2.VideoCapture(i_video) + num_frame = cap.get(cv2.CAP_PROP_FRAME_COUNT) + expand_name = '.jpg' + if not cap.isOpened(): + print("Please check the path.") + cnt = 0 + count = 0 + while 1: + ret, frame = cap.read() + cnt += 1 + # how + # many + # frame + # to + # cut + if cnt % num == 0: + count += 1 + cv2.imwrite(os.path.join(o_video, str(count) + expand_name), frame) + + if not ret: + break + + +if __name__ == '__main__': + args = parse_args() + if not os.path.exists(args.output): + os.makedirs(args.output) + print('Called with args:') + print(args) + process_video(args.input, args.output, args.skip_frame) diff --git a/src/SanDCJ/xiangsu_shuai.py b/src/SanDCJ/xiangsu_shuai.py new file mode 100644 index 00000000..64604d0e --- /dev/null +++ b/src/SanDCJ/xiangsu_shuai.py @@ -0,0 +1,22 @@ +import os +from PIL import Image, ImageFile + +# ImageFile.LOAD_TRUNCATED_IMAGES = True #如果图片太大报错可以使用这个 + + + +def pixel(): + b = 0 + dir = r'C:\Users\xinluo\PycharmProjects\pythonProject\Camera Roll' # 需要处理的图片目录 + files = os.listdir(dir) # 得到需要处理的所有图片 + files.sort() # 对图片进行排序 + for each_bmp in files: # 遍历图片,进行筛选 + + each_bmp_root = os.path.join(dir, each_bmp) # 得到每个图片路径 + image = Image.open(each_bmp_root) # 打开每个图片 + img = image.convert('RGB') # 转化成RGB,其实图片大多都是RGB,即使灰度图也不一定转RGB,看需求 + width = img.size[0] # 获取图像的宽和长 + height = img.size[1] + + if (width < 650) or (height < 650): # 判断图形尺寸有一条边小于600像素的直接删除 + os.remove(each_bmp_root) diff --git a/src/calib_IR.py b/src/calib_IR.py deleted file mode 100644 index 24dcfeeb..00000000 --- a/src/calib_IR.py +++ /dev/null @@ -1,90 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Homework: Calibrate the Camera with ZhangZhengyou Method. -Picture File Folder: ".\pic\IR_camera_calib_img", With Distort. - -By YouZhiyuan 2019.11.18 -""" - -import os -import numpy as np -import cv2 -import glob - - -def calib(inter_corner_shape, size_per_grid, img_dir,img_type): - # criteria: only for subpix calibration, which is not used here. - # criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001) - w,h = inter_corner_shape - # cp_int: corner point in int form, save the coordinate of corner points in world sapce in 'int' form - # like (0,0,0), (1,0,0), (2,0,0) ....,(10,7,0). - cp_int = np.zeros((w*h,3), np.float32) - cp_int[:,:2] = np.mgrid[0:w,0:h].T.reshape(-1,2) - # cp_world: corner point in world space, save the coordinate of corner points in world space. - cp_world = cp_int*size_per_grid - - obj_points = [] # the points in world space - img_points = [] # the points in image space (relevant to obj_points) - images = glob.glob(img_dir + os.sep + '**.' + img_type) - for fname in images: - img = cv2.imread(fname) - gray_img = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY) - # find the corners, cp_img: corner points in pixel space. - ret, cp_img = cv2.findChessboardCorners(gray_img, (w,h), None) - # if ret is True, save. - if ret == True: - # cv2.cornerSubPix(gray_img,cp_img,(11,11),(-1,-1),criteria) - obj_points.append(cp_world) - img_points.append(cp_img) - # view the corners - cv2.drawChessboardCorners(img, (w,h), cp_img, ret) - cv2.imshow('FoundCorners',img) - cv2.waitKey(1) - cv2.destroyAllWindows() - # calibrate the camera - ret, mat_inter, coff_dis, v_rot, v_trans = cv2.calibrateCamera(obj_points, img_points, gray_img.shape[::-1], None, None) - print (("ret:"),ret) - print (("internal matrix:\n"),mat_inter) - # in the form of (k_1,k_2,p_1,p_2,k_3) - print (("distortion cofficients:\n"),coff_dis) - print (("rotation vectors:\n"),v_rot) - print (("translation vectors:\n"),v_trans) - # calculate the error of reproject - total_error = 0 - for i in range(len(obj_points)): - img_points_repro, _ = cv2.projectPoints(obj_points[i], v_rot[i], v_trans[i], mat_inter, coff_dis) - error = cv2.norm(img_points[i], img_points_repro, cv2.NORM_L2)/len(img_points_repro) - total_error += error - print(("Average Error of Reproject: "), total_error/len(obj_points)) - - return mat_inter, coff_dis - -def dedistortion(inter_corner_shape, img_dir,img_type, save_dir, mat_inter, coff_dis): - w,h = inter_corner_shape - images = glob.glob(img_dir + os.sep + '**.' + img_type) - for fname in images: - img_name = fname.split(os.sep)[-1] - img = cv2.imread(fname) - newcameramtx, roi = cv2.getOptimalNewCameraMatrix(mat_inter,coff_dis,(w,h),0,(w,h)) # 自由比例参数 - dst = cv2.undistort(img, mat_inter, coff_dis, None, newcameramtx) - # clip the image - # x,y,w,h = roi - # dst = dst[y:y+h, x:x+w] - cv2.imwrite(save_dir + os.sep + img_name, dst) - print('Dedistorted images have been saved to: %s successfully.' %save_dir) - -if __name__ == '__main__': - inter_corner_shape = (11,8) - size_per_grid = 0.02 - img_dir = ".\\pic\\IR_camera_calib_img" - img_type = "png" - # calibrate the camera - mat_inter, coff_dis = calib(inter_corner_shape, size_per_grid, img_dir,img_type) - # dedistort and save the dedistortion result. - save_dir = ".\\pic\\save_dedistortion" - if(not os.path.exists(save_dir)): - os.makedirs(save_dir) - dedistortion(inter_corner_shape, img_dir, img_type, save_dir, mat_inter, coff_dis) - - - \ No newline at end of file diff --git a/src/main.py b/src/main.py deleted file mode 100644 index 9c67096c..00000000 --- a/src/main.py +++ /dev/null @@ -1,353 +0,0 @@ -# -*- coding: utf-8 -*- - -# Form implementation generated from reading ui file 'main.py' -# -# Created by: PyQt5 UI code generator 5.12.3 -# -# WARNING! All changes made in this file will be lost! -import os -import cv2 -import json -import numpy as np -import glob -from PIL import Image -import matplotlib.pyplot as plt -plt.rcParams['font.sans-serif'] = ['SimHei'] -plt.rcParams['axes.unicode_minus'] = False - - -def cart2hom(arr): - """ Convert catesian to homogenous points by appending a row of 1s - :param arr: array of shape (num_dimension x num_points) - :returns: array of shape ((num_dimension+1) x num_points) - """ - if arr.ndim == 1: - return np.hstack([arr, 1]) - return np.asarray(np.vstack([arr, np.ones(arr.shape[1])])) - - -def compute_P_from_essential(E): - """ Compute the second camera matrix (assuming P1 = [I 0]) - from an essential matrix. E = [t]R - :returns: list of 4 possible camera matrices. - """ - U, S, V = np.linalg.svd(E) - - # Ensure rotation matrix are right-handed with positive determinant - if np.linalg.det(np.dot(U, V)) < 0: - V = -V - - # create 4 possible camera matrices (Hartley p 258) - W = np.array([[0, -1, 0], [1, 0, 0], [0, 0, 1]]) - P2s = [np.vstack((np.dot(U, np.dot(W, V)).T, U[:, 2])).T, - np.vstack((np.dot(U, np.dot(W, V)).T, -U[:, 2])).T, - np.vstack((np.dot(U, np.dot(W.T, V)).T, U[:, 2])).T, - np.vstack((np.dot(U, np.dot(W.T, V)).T, -U[:, 2])).T] - - return P2s - - -def correspondence_matrix(p1, p2): - p1x, p1y = p1[:2] - p2x, p2y = p2[:2] - - return np.array([ - p1x * p2x, p1x * p2y, p1x, - p1y * p2x, p1y * p2y, p1y, - p2x, p2y, np.ones(len(p1x)) - ]).T - - return np.array([ - p2x * p1x, p2x * p1y, p2x, - p2y * p1x, p2y * p1y, p2y, - p1x, p1y, np.ones(len(p1x)) - ]).T - - -def scale_and_translate_points(points): - """ Scale and translate image points so that centroid of the points - are at the origin and avg distance to the origin is equal to sqrt(2). - :param points: array of homogenous point (3 x n) - :returns: array of same input shape and its normalization matrix - """ - x = points[0] - y = points[1] - center = points.mean(axis=1) # mean of each row - cx = x - center[0] # center the points - cy = y - center[1] - dist = np.sqrt(np.power(cx, 2) + np.power(cy, 2)) - scale = np.sqrt(2) / dist.mean() - norm3d = np.array([ - [scale, 0, -scale * center[0]], - [0, scale, -scale * center[1]], - [0, 0, 1] - ]) - - return np.dot(norm3d, points), norm3d - - -def compute_image_to_image_matrix(x1, x2, compute_essential=False): - """ Compute the fundamental or essential matrix from corresponding points - (x1, x2 3*n arrays) using the 8 point algorithm. - Each row in the A matrix below is constructed as - [x'*x, x'*y, x', y'*x, y'*y, y', x, y, 1] - """ - A = correspondence_matrix(x1, x2) - # compute linear least square solution - U, S, V = np.linalg.svd(A) - F = V[-1].reshape(3, 3) - - # constrain F. Make rank 2 by zeroing out last singular value - U, S, V = np.linalg.svd(F) - S[-1] = 0 - if compute_essential: - S = [1, 1, 0] # Force rank 2 and equal eigenvalues - F = np.dot(U, np.dot(np.diag(S), V)) - - return F - - -def compute_normalized_image_to_image_matrix(p1, p2, compute_essential=False): - """ Computes the fundamental or essential matrix from corresponding points - using the normalized 8 point algorithm. - :input p1, p2: corresponding points with shape 3 x n - :returns: fundamental or essential matrix with shape 3 x 3 - """ - n = p1.shape[1] - if p2.shape[1] != n: - raise ValueError('Number of points do not match.') - - # preprocess image coordinates - p1n, T1 = scale_and_translate_points(p1) - p2n, T2 = scale_and_translate_points(p2) - - # compute F or E with the coordinates - F = compute_image_to_image_matrix(p1n, p2n, compute_essential) - - # reverse preprocessing of coordinates - # We know that P1' E P2 = 0 - F = np.dot(T1.T, np.dot(F, T2)) - - return F / F[2, 2] - - -def compute_fundamental_normalized(p1, p2): - return compute_normalized_image_to_image_matrix(p1, p2) - - -def compute_essential_normalized(p1, p2): - return compute_normalized_image_to_image_matrix(p1, p2, compute_essential=True) - - -def skew(x): - """ Create a skew symmetric matrix *A* from a 3d vector *x*. - Property: np.cross(A, v) == np.dot(x, v) - :param x: 3d vector - :returns: 3 x 3 skew symmetric matrix from *x* - """ - return np.array([ - [0, -x[2], x[1]], - [x[2], 0, -x[0]], - [-x[1], x[0], 0] - ]) - - -def reconstruct_one_point(pt1, pt2, m1, m2): - """ - pt1 and m1 * X are parallel and cross product = 0 - pt1 x m1 * X = pt2 x m2 * X = 0 - """ - A = np.vstack([ - np.dot(skew(pt1), m1), - np.dot(skew(pt2), m2) - ]) - U, S, V = np.linalg.svd(A) - P = np.ravel(V[-1, :4]) - - return P / P[3] - - -def linear_triangulation(p1, p2, m1, m2): - """ - Linear triangulation (Hartley ch 12.2 pg 312) to find the 3D point X - where p1 = m1 * X and p2 = m2 * X. Solve AX = 0. - :param p1, p2: 2D points in homo. or catesian coordinates. Shape (3 x n) - :param m1, m2: Camera matrices associated with p1 and p2. Shape (3 x 4) - :returns: 4 x n homogenous 3d triangulated points - """ - num_points = p1.shape[1] - res = np.ones((4, num_points)) - - for i in range(num_points): - A = np.asarray([ - (p1[0, i] * m1[2, :] - m1[0, :]), - (p1[1, i] * m1[2, :] - m1[1, :]), - (p2[0, i] * m2[2, :] - m2[0, :]), - (p2[1, i] * m2[2, :] - m2[1, :]) - ]) - - _, _, V = np.linalg.svd(A) - X = V[-1, :4] - res[:, i] = X / X[3] - - return res - - -def writetofile(dict, path): - for index, item in enumerate(dict): - dict[item] = np.array(dict[item]) - dict[item] = dict[item].tolist() - js = json.dumps(dict) - with open(path, 'w') as f: - f.write(js) - print("参数已成功保存到文件") - - -def readfromfile(path): - with open(path, 'r') as f: - js = f.read() - mydict = json.loads(js) - print("参数读取成功") - return mydict - - -def camera_calibration(SaveParamPath, ImagePath): - # 循环中断 - criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001) - # 棋盘格尺寸 - row = 11 - column = 8 - objpoint = np.zeros((row * column, 3), np.float32) - objpoint[:, :2] = np.mgrid[0:row, 0:column].T.reshape(-1, 2) - - objpoints = [] # 3d point in real world space - imgpoints = [] # 2d points in image plane. - batch_images = glob.glob(ImagePath +os.sep+ '**.jpg') - for i, fname in enumerate(batch_images): - img = cv2.imread(batch_images[i]) - imgGray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) - # find chess board corners - ret, corners = cv2.findChessboardCorners(imgGray, (row, column), None) - # if found, add object points, image points (after refining them) - if ret == True: - #objpoints.append(objpoint) - #corners2 = cv2.cornerSubPix(imgGray, corners, (11, 11), (-1, -1), criteria) - #imgpoints.append(corners2) - objpoints.append(objpoint) - imgpoints.append(corners) - # Draw and display the corners - img = cv2.drawChessboardCorners(img, (row, column), corners, ret)#corners - cv2.imwrite('Checkerboard_Image/Temp_JPG/Temp_' + str(i) + '.jpg', img) - print("成功提取:", len(batch_images), "张图片角点!") - - ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(objpoints, imgpoints, imgGray.shape[::-1], None, None) - dict = {'ret': ret, 'mtx': mtx, 'dist': dist, 'rvecs': rvecs, 'tvecs': tvecs} - writetofile(dict, SaveParamPath) - - meanError = 0 - for i in range(len(objpoints)): - imgpoints2, _ = cv2.projectPoints(objpoints[i], rvecs[i], tvecs[i], mtx, dist) - error = cv2.norm(imgpoints[i], imgpoints2, cv2.NORM_L2) / len(imgpoints2) - meanError += error - print("total error: ", meanError / len(objpoints)) - - -def epipolar_geometric(Images_Path, K): - IMG = glob.glob(Images_Path) - img1, img2 = cv2.imread(IMG[0]), cv2.imread(IMG[1]) - img1_gray = cv2.cvtColor(img1, cv2.COLOR_BGR2GRAY) - img2_gray = cv2.cvtColor(img2, cv2.COLOR_BGR2GRAY) - - # Initiate SURF detector - SURF = cv2.xfeatures2d.SURF_create() - - # compute keypoint & descriptions - keypoint1, descriptor1 = SURF.detectAndCompute(img1_gray, None) - keypoint2, descriptor2 = SURF.detectAndCompute(img2_gray, None) - print("角点数量:", len(keypoint1), len(keypoint2)) - - # Find point matches - bf = cv2.BFMatcher(cv2.NORM_L2, crossCheck=True) - matches = bf.match(descriptor1, descriptor2) - print("匹配点数量:", len(matches)) - - src_pts = np.asarray([keypoint1[m.queryIdx].pt for m in matches]) - dst_pts = np.asarray([keypoint2[m.trainIdx].pt for m in matches]) - # plot - knn_image = cv2.drawMatches(img1_gray, keypoint1, img2_gray, keypoint2, matches[:-1], None, flags=2) - image_ = Image.fromarray(np.uint8(knn_image)) - image_.save("MatchesImage.jpg") - - # Constrain matches to fit homography - retval, mask = cv2.findHomography(src_pts, dst_pts, cv2.RANSAC, 100.0) - - # We select only inlier points - points1 = src_pts[mask.ravel() == 1] - points2 = dst_pts[mask.ravel() == 1] - - points1 = cart2hom(points1.T) - points2 = cart2hom(points2.T) - # plot - fig, ax = plt.subplots(1, 2) - ax[0].autoscale_view('tight') - ax[0].imshow(cv2.cvtColor(img1, cv2.COLOR_BGR2RGB)) - ax[0].plot(points1[0], points1[1], 'r.') - ax[1].autoscale_view('tight') - ax[1].imshow(cv2.cvtColor(img2, cv2.COLOR_BGR2RGB)) - ax[1].plot(points2[0], points2[1], 'r.') - plt.savefig('MatchesPoints.jpg') - fig.show() - # - - points1n = np.dot(np.linalg.inv(K), points1) - points2n = np.dot(np.linalg.inv(K), points2) - E = compute_essential_normalized(points1n, points2n) - print('Computed essential matrix:', (-E / E[0][1])) - - P1 = np.array([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0]]) - P2s = compute_P_from_essential(E) - - ind = -1 - for i, P2 in enumerate(P2s): - # Find the correct camera parameters - d1 = reconstruct_one_point(points1n[:, 0], points2n[:, 0], P1, P2) - # Convert P2 from camera view to world view - P2_homogenous = np.linalg.inv(np.vstack([P2, [0, 0, 0, 1]])) - d2 = np.dot(P2_homogenous[:3, :4], d1) - if d1[2] > 0 and d2[2] > 0: - ind = i - - P2 = np.linalg.inv(np.vstack([P2s[ind], [0, 0, 0, 1]]))[:3, :4] - Points3D = linear_triangulation(points1n, points2n, P1, P2) - - return Points3D - - -def main(): - CameraParam_Path = 'CameraParam.txt' - CheckerboardImage_Path = '.\\Checkerboard_Image' - Images_Path = 'SubstitutionCalibration_Image/*.jpg' - - # 计算相机参数 - camera_calibration(CameraParam_Path, CheckerboardImage_Path) - # 读取相机参数 - config = readfromfile(CameraParam_Path) - K = np.array(config['mtx']) - # 计算3D点 - Points3D = epipolar_geometric(Images_Path, K) - # 重建3D点 - fig = plt.figure() - fig.suptitle('3D reconstructed', fontsize=16) - ax = fig.gca(projection='3d') - ax.plot(Points3D[0], Points3D[1], Points3D[2], 'b.') - ax.set_xlabel('x axis') - ax.set_ylabel('y axis') - ax.set_zlabel('z axis') - ax.view_init(elev=135, azim=90) - plt.savefig('Reconstruction.jpg') - plt.show() - - -if __name__ == '__main__': - main() - diff --git a/src/CameraParam.txt b/src/output/CameraParam.txt similarity index 100% rename from src/CameraParam.txt rename to src/output/CameraParam.txt diff --git a/src/MatchesImage.jpg b/src/output/MatchesImage.jpg similarity index 100% rename from src/MatchesImage.jpg rename to src/output/MatchesImage.jpg diff --git a/src/MatchesPoints.jpg b/src/output/MatchesPoints.jpg similarity index 100% rename from src/MatchesPoints.jpg rename to src/output/MatchesPoints.jpg diff --git a/src/Reconstruction.jpg b/src/output/Reconstruction.jpg similarity index 100% rename from src/Reconstruction.jpg rename to src/output/Reconstruction.jpg diff --git a/src/readme.txt b/src/readme.txt index 5e5a8952..9a76bad3 100644 --- a/src/readme.txt +++ b/src/readme.txt @@ -1 +1,9 @@ -储存源代码 +video文件夹储存着视频; +Saved Pictures文件夹储存视频分帧之后得到的照片 +Camera Roll文件夹储存照片经过灰度值、像素、清晰度三次筛选之后的照片 +SubstitutionCalibration_Image_1文件夹储存将用来三维重建的照片 +pic/RGB_camera_calib_img_1文件夹储存用来标定相机的棋盘照片,照片为10*7大小,边长2.1cm +output/CameraParam.txt储存标定相机之后得到的相机参数 +output文件夹中三个照片分别为:MatchesImage.jpg为匹配点对的连线图;MatchesPoints.jpg为匹配点对图;Reconstruction.jpg为初步尝试三维重建,得到的三维点图。 +SanDCJ中为主要代码,video.py负责ui界面及和无人机连接;calib_RGB.py实现三维重建;video_to_photo.py实现视频提取照片;huidu_shuai.py、qingxi_shuai.py、xiangsu_shuai.py分别为从灰度值、清晰度、像素三个方面筛选照片 + diff --git a/src/test.py b/src/test.py deleted file mode 100644 index f3b42d6b..00000000 --- a/src/test.py +++ /dev/null @@ -1,69 +0,0 @@ -import sys - -from PyQt5 import QtCore, QtGui, QtWidgets -import numpy as np -import matplotlib -matplotlib.use("Qt5Agg") # 声明使用pyqt5 -from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg # pyqt5的画布 -import matplotlib.pyplot as plt -# matplotlib.figure 模块提供了顶层的Artist(图中的所有可见元素都是Artist的子类),它包含了所有的plot元素 -from matplotlib.figure import Figure - -class MyMatplotlibFigure(FigureCanvasQTAgg): - """ - 创建一个画布类,并把画布放到FigureCanvasQTAgg - """ - def __init__(self, width=10, heigh=10, dpi=100): - plt.rcParams['figure.facecolor'] = 'r' # 设置窗体颜色 - plt.rcParams['axes.facecolor'] = 'b' # 设置绘图区颜色 - self.width_ = width - self.heigh_ = heigh - self.dpi = dpi - self.figs = Figure(figsize=(self.width_, self.heigh_), dpi=self.dpi) - super(MyMatplotlibFigure, self).__init__(self.figs) # 在父类种激活self.fig, 否则不能显示图像 - self.axes = self.figs.add_subplot(111) - - def mat_plot_drow_axes(self, t, s): - - """ - 用清除画布刷新的方法绘图 - :return: - """ - self.axes.cla() # 清除绘图区 - self.axes.spines['top'].set_visible(False) # 顶边界不可见 - self.axes.spines['right'].set_visible(False) # 右边界不可见 - # 设置左、下边界在(0,0)处相交 - # self.axes.spines['bottom'].set_position(('data', 0)) # 设置y轴线原点数据为 0 - self.axes.spines['left'].set_position(('data', 0)) # 设置x轴线原点数据为 0 - self.axes.plot(t, s, 'o-r', linewidth=0.5) - self.figs.canvas.draw() # 这里注意是画布重绘,self.figs.canvas - self.figs.canvas.flush_events() # 画布刷新self.figs.canvas -class MainDialogImgBW_(QtWidgets.QMainWindow): - """ - 创建UI主窗口,使用画板类绘图。 - """ - def __init__(self): - super(MainDialogImgBW_, self).__init__() - self.setWindowTitle("显示matplotlib") - self.setObjectName("widget") - self.resize(800, 600) - self.label = QtWidgets.QLabel(self) - self.label.setGeometry(QtCore.QRect(0, 0, 800, 600)) - #实例化自定义画布类 - self.canvas = MyMatplotlibFigure(width=5, heigh=4, dpi=100) - self.plotcos() - self.hboxlayout = QtWidgets.QHBoxLayout(self.label) - self.hboxlayout.addWidget(self.canvas) - - def plotcos(self): - # plt.clf() - t = np.arange(0.0, 5.0, 0.01) - s = np.cos(2 * np.pi * t) - self.canvas.mat_plot_drow_axes(t, s) - self.canvas.figs.suptitle("sin") # 设置标题 - -if __name__ == "__main__": - app = QtWidgets.QApplication(sys.argv) - main = MainDialogImgBW_() - main.show() - sys.exit(app.exec_()) \ No newline at end of file diff --git a/src/untitled.py b/src/untitled.py deleted file mode 100644 index 4fc3bf6c..00000000 --- a/src/untitled.py +++ /dev/null @@ -1,45 +0,0 @@ -# -*- coding: utf-8 -*- - -# Form implementation generated from reading ui file 'untitled.ui' -# -# Created by: PyQt5 UI code generator 5.12.3 -# -# WARNING! All changes made in this file will be lost! - - -from PyQt5 import QtCore, QtGui, QtWidgets - - -class Ui_MainWindow(object): - def setupUi(self, MainWindow): - MainWindow.setObjectName("MainWindow") - MainWindow.resize(800, 600) - self.centralwidget = QtWidgets.QWidget(MainWindow) - self.centralwidget.setObjectName("centralwidget") - self.pushButton = QtWidgets.QPushButton(self.centralwidget) - self.pushButton.setGeometry(QtCore.QRect(110, 100, 56, 17)) - self.pushButton.setObjectName("pushButton") - self.checkBox = QtWidgets.QCheckBox(self.centralwidget) - self.checkBox.setGeometry(QtCore.QRect(190, 100, 54, 13)) - self.checkBox.setObjectName("checkBox") - MainWindow.setCentralWidget(self.centralwidget) - self.menubar = QtWidgets.QMenuBar(MainWindow) - self.menubar.setGeometry(QtCore.QRect(0, 0, 800, 18)) - self.menubar.setObjectName("menubar") - MainWindow.setMenuBar(self.menubar) - self.statusbar = QtWidgets.QStatusBar(MainWindow) - self.statusbar.setObjectName("statusbar") - MainWindow.setStatusBar(self.statusbar) - - self.retranslateUi(MainWindow) - QtCore.QMetaObject.connectSlotsByName(MainWindow) - - def retranslateUi(self, MainWindow): - _translate = QtCore.QCoreApplication.translate - MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow")) - self.pushButton.setText(_translate("MainWindow", "PushButton")) - self.checkBox.setText(_translate("MainWindow", "CheckBox")) - - -class Ui_Form: - pass \ No newline at end of file diff --git a/src/untitled.ui b/src/untitled.ui deleted file mode 100644 index c254ceae..00000000 --- a/src/untitled.ui +++ /dev/null @@ -1,58 +0,0 @@ - - - MainWindow - - - - 0 - 0 - 800 - 600 - - - - MainWindow - - - - - - 110 - 100 - 56 - 17 - - - - PushButton - - - - - - 190 - 100 - 54 - 13 - - - - CheckBox - - - - - - - 0 - 0 - 800 - 18 - - - - - - - - diff --git a/src/1234.mp4 b/src/video/1234.mp4 similarity index 100% rename from src/1234.mp4 rename to src/video/1234.mp4