C++学习笔记——opencv2模块(图像处理)

  • 1. 使用方式
  • 2. 注意点
  • 3. 图片读写与属性
    • 3.1 图片基本操作
    • 3.2 遍历像素点
    • 3.3 提取指定像素值
    • 3.4 初始化为0的矩阵
    • 3.5 计算某个灰度切片区域的像素和
  • 4. 切片,浅拷贝与深拷贝
  • 5. 图片操作函数
    • 5.1 图片翻转
    • 5.2 二值化
    • 5.3 轮廓检测
    • 5.4 计算均值与标准差
  • 6. 一些代码样例
    • 6.1 去畸变代码
    • 6.2 ORB特征匹配代码
    • 6.3 将特征数据写入磁盘/从磁盘读取特征数据并匹配输出
  • 7. cv::Mat与Eigen::Matrix互相转换
  • 8. 在窗口中显示放大后的图像

用于计算图像处理的opencv2,只不过这次用的不是python的版本,而是C++的版本。

参考书籍:《视觉SLAM十四讲-从理论到实践》——高翔

1. 使用方式

CMakeLists.txt写法样例:

# 添加c++11标准支持
set(CMAKE_CXX_FLAGS "-std=c++11")# cmake最低版本需求
cmake_minimum_required(VERSION 2.8)# 创建项目
project( test )# 创建执行程序
add_executable( cv2_test cv2_test.cpp )# 寻找opencv
find_package( OpenCV REQUIRED )
# 添加头文件
include_directories(${OpenCV_INCLUDE_DIRS})
# 链接opencv库
target_link_libraries( cv2_test ${OpenCV_LIBS} )

2. 注意点

读取数据的数据类型使用unsigned char:因为int类型在不同操作系统平台下长度不同,而uchar类型在所有平台上长度都是一样的。

3. 图片读写与属性

3.1 图片基本操作

#include <iostream>// 导入cv2模块
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>// 在不考虑细致实现的情况下,粗暴导入所有cv2模块
// #include <opencv2/opencv.hpp>using namespace std;
using namespace cv;int main(int argc, char **argv) {// 图片路径,可以写字符串,也可以用argv从启动参数导入// 注意在IDE里面编译时需要考虑到当前运行路径在build文件夹内string img_path = "../test.jpeg";// 创建图片变量cv::Mat image;// 读图片image = cv::imread(img_path);// 验证图片是否正确读取,如果data为空指针说明读取失败if (image.data == nullptr) {cerr << "图片" << img_path << "读取失败。" << endl;return -1;}// 打开一个新窗口,显示读取成功的图片cv::imshow("窗口的标题", image);// 保持窗口打开等待关闭cv::waitKey(0);// 输出图片类型,直接输出会是一个整数cout << "图片类型为:" << image.type() << endl;// 但是作为哈希值可以直接用来判断图片类型// CV_8UC3表示8bit,unsigned int,图像3通道,灰度图就是1通道// float 32位,double64位// S(signed int),U(unsigned int),F(float)if (image.type() == CV_8UC3) {cout << "这是彩色8比特图片。" << endl;}else {cout << "这不是彩色8比特图片。" << endl;}// 获取图片尺寸信息cout << "宽度:" << image.cols;cout << ",高度:" << image.rows;cout << ",频道数:" << image.channels() << endl;// 创建新图像[以灰度图为例]// int rows = image.rows, cols = image.cols;// cv::Mat image_new = cv::Mat(rows, cols, CV_8UC1);// 遍历图像与指针for (size_t y = 0; y < image.rows; y++) {// 行指针类型cv::Mat::ptr// 使用每一行的头部指针作为后续遍历基础unsigned char *row_ptr = image.ptr<unsigned char>(y);// 然后对每一行进行遍历for (size_t x = 0; x < image.cols; x++) {// 指向一个具体的像素点的指针// 按照【像素数*通道属性】向后移动unsigned char *data_ptr = &row_ptr[x*image.channels()];// 遍历每一个通道for (int c = 0; c != image.channels(); c++) {unsigned char data = data_ptr[c];// 显示时需要做个类型转换修改为int类型cout << (int)data << " ";}// 以每一行一个像素点的方式显示// 例如:217 215 221cout << endl;}}// 直接取出指定像素// 此处彩色图片,灰色单通道图片则使用image.at<uchar>// 第0行第0列的像素cv::Vec3b pixel = image.at<cv::Vec3b>(0, 0);cout << "第一个像素点是:" << pixel << endl;cout << "第一个像素点的第一个通道是:" << (int)pixel[0] << endl;// 保存图片cv::imwrite( "../test2.jpeg", image);}

运行得到:
【图就不放了】

图片类型为:16
这是彩色8比特图片。
宽度:550,高度:827,频道数:3
254 254 254
206 206 218
207 207 219
208 208 220
208 208 220
……
第一个像素点是:Ä254, 254, 254Å
第一个像素点的第一个通道是:254

3.2 遍历像素点

较慢的遍历图片中每个像素点(灰度)

for (auto p = img.begin<uchar>(); p !=  img.end<uchar>(); ++p){uchar value = uchar((*p));}

更快的像素点指针遍历法(灰度):

for (int i = 0; i <img.cols * img.rows; i++){uchar value = img.data[i];}

3.3 提取指定像素值

用at的方法按照行列提取像素值(慢)

// 遍历行for (int row = 0; row < grayImg.rows; row++){// 遍历列for (int col = 0; col < grayImg.cols; col++){   point_value = int(grayImg.at<uchar>(row, col));}}

用指针按照行列提取像素值(快)

point_value = float(grayImg.data[row * grayImg.cols + col]);

3.4 初始化为0的矩阵

cv::Mat temp_img;
// 初始化为0
temp_img = cv::Mat::zeros(rows, cols, CV_8UC1);

3.5 计算某个灰度切片区域的像素和

float sum_bright  = cv::sum(GrayImg(cv::Rect(col_min,row_min,w,h)))[0]

4. 切片,浅拷贝与深拷贝

#include <iostream>// 导入cv2模块
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>// 在不考虑细致实现的情况下,粗暴导入所有cv2模块
// #include <opencv2/opencv.hpp>using namespace std;
using namespace cv;int main(int argc, char **argv) {// 图片路径,可以写字符串,也可以用argv从启动参数导入// 注意在IDE里面编译时需要考虑到当前运行路径在build文件夹内string img_path = "../test.jpeg";// 创建图片变量cv::Mat image;// 读图片image = cv::imread(img_path);// 验证图片是否正确读取,如果data为空指针说明读取失败if (image.data == nullptr) {cerr << "图片" << img_path << "读取失败。" << endl;return -1;}// 浅拷贝,并不会复制内存数据cv::Mat image_shallow_copy = image;// “切片”,将左上角100*100的块置零image_shallow_copy( cv::Rect(0, 0, 100, 100)).setTo(0);// 显示原图片的像素,发现被改变了cout << "浅拷贝图片的第一个像素:" << image_shallow_copy.at<cv::Vec3b>(0, 0) << endl;cout << "原始图片的第一个像素:" << image.at<cv::Vec3b>(0, 0) << endl;// 深拷贝,会复制内存数据cv::Mat image_deep_copy = image.clone();// “切片”,将左上角100*100的块置255image_deep_copy( cv::Rect(0, 0, 100, 100)).setTo(255);// 显示原图片的像素,发现被改变了cout << "深拷贝图片的第一个像素:" << image_deep_copy.at<cv::Vec3b>(0, 0) << endl;cout << "原始图片的第一个像素:" << image.at<cv::Vec3b>(0, 0) << endl;
}

运行得到:

浅拷贝图片的第一个像素:Ä0, 0, 0Å
原始图片的第一个像素:Ä0, 0, 0Å
深拷贝图片的第一个像素:Ä255, 255, 255Å
原始图片的第一个像素:Ä0, 0, 0Å

5. 图片操作函数

5.1 图片翻转

// 翻转图片
// 水平翻转
cv::flip(image_input, image_output, 1);
// 垂直翻转
cv::flip(image_input, image_output, 0);
// 水平垂直翻转
cv::flip(image_input, image_output, -1);

5.2 二值化

// 固定阈值
cv::threshold(img, binary, Threshold, 255, cv::THRESH_BINARY);// 大津法
int Threshold = cv::threshold(img, binary, 0, 255, cv::THRESH_BINARY + cv::THRESH_OTSU);

5.3 轮廓检测

// 对二值化图像进行轮廓检测
std::vector< std::vector< cv::Point> > contours;
cv::findContours(bianry, contours, cv::noArray(),cv::RETR_EXTERNAL,cv::CHAIN_APPROX_SIMPLE);

5.4 计算均值与标准差

cv::meanStdDev()

6. 一些代码样例

6.1 去畸变代码

来自参考书籍:《视觉SLAM十四讲-从理论到实践》——高翔

#include <opencv2/opencv.hpp>
#include <string>
using namespace std;// 也可以直接通过库函数cv::Undisort()实现// 图片路径
string image_file = "../distorted.png";int main(int argc, char **argv) {// 配置畸变参数double k1 = -0.28340811, k2 = 0.07395907, p1 = 0.00019359, p2 = 1.76187114e-05;// 配置内部参数double fx = 458.654, fy = 457.296, cx = 367.215, cy = 248.375;// CV_8UC1灰度图cv::Mat image = cv::imread(image_file, 0);// 定义去畸变后的新图像int rows = image.rows, cols = image.cols;cv::Mat image_undistort = cv::Mat(rows, cols, CV_8UC1);// 计算去畸变后的图像for (int v=0; v<rows; v++) {for (int u=0; u<cols; u++) {// 将当前的(u, v)对应到畸变图像中的(u_distorted, v_distorted)double x = (u-cx) / fx, y = (v-cy) / fy;double r = sqrt(x * x + y * y);double x_distorted = x * (1+ k1*r*r + k2*r*r*r*r) + 2*p1*x*y + p2*(r*r+2*x*x);double y_distorted = y * (1+ k1*r*r + k2*r*r*r*r) + 2*p2*x*y + p1*(r*r+2*y*y);double u_distorted = fx * x_distorted + cx;double v_distorted = fy * y_distorted + cy;// 使用最近邻插值进行赋值if (u_distorted >= 0 && v_distorted >=0 && u_distorted < cols && v_distorted < rows) {image_undistort.at<uchar>(v, u) = image.at<uchar>((int) v_distorted, (int) u_distorted);}else {image_undistort.at<uchar>(v, u) = 0;}}}// 显示去畸变图cv::imshow("distored", image);cv::imshow("undistored", image_undistort);cv::waitKey();return 0;
}

6.2 ORB特征匹配代码

来自参考书籍:《视觉SLAM十四讲-从理论到实践》——高翔

#include <iostream>
#include <opencv2/core/core.hpp>
#include <opencv2/features2d/features2d.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <chrono>using namespace std;
using namespace cv;// 使用opencv实现ORB特征匹配int main(int argc, char **argv)
{// 为了在ide里面方便运行,暂时改为写死的固定路径,注释掉原作者的这部分代码// if (argc != 3) {//   cout << "usage: feature_extraction img1 img2" << endl;//   return 1;// }// 读取图像,修改为从固定路径读取// 因为在build文件夹内,因此进入上级目录Mat img_1 = imread("../1.png", CV_LOAD_IMAGE_COLOR);Mat img_2 = imread("../2.png", CV_LOAD_IMAGE_COLOR);// 用断言确保两张图片都已经成功被读取assert(img_1.data != nullptr && img_2.data != nullptr);// 创建两组关键点的vectorstd::vector<KeyPoint> keypoints_1, keypoints_2;// 创建两个矩阵Mat descriptors_1, descriptors_2;// 初始化// 提取FAST关键点用的工具Ptr<FeatureDetector> detector = ORB::create();// 提取BRIEF描述子用的工具Ptr<DescriptorExtractor> descriptor = ORB::create();// 使用汉明距离作为匹配标准Ptr<DescriptorMatcher> matcher = DescriptorMatcher::create("BruteForce-Hamming");// 第一步:检测 Oriented FAST 角点位置// 检测半径为3的圆上是否有足够多的点和当前点比起来亮度差异超过一定比例// 基于图像金字塔的上下层来构建尺度不变性// 基于灰度质心法构建旋转不变性chrono::steady_clock::time_point t1 = chrono::steady_clock::now();detector->detect(img_1, keypoints_1);detector->detect(img_2, keypoints_2);// 第二步:根据FAST角点位置计算 BRIEF 描述子descriptor->compute(img_1, keypoints_1, descriptors_1);descriptor->compute(img_2, keypoints_2, descriptors_2);// 结束检测部分,计算消耗时间chrono::steady_clock::time_point t2 = chrono::steady_clock::now();chrono::duration<double> time_used = chrono::duration_cast<chrono::duration<double>>(t2 - t1);cout << "extract ORB cost = " << time_used.count() << " seconds. " << endl;Mat outimg1;// 绘制并显示特征图drawKeypoints(img_1, keypoints_1, outimg1, Scalar::all(-1), DrawMatchesFlags::DEFAULT);imshow("ORB features", outimg1);// 第三步:对两幅图像中的BRIEF描述子进行匹配,使用 Hamming 距离作为匹配标准vector<DMatch> matches;t1 = chrono::steady_clock::now();// 匹配2张图像的描述子matcher->match(descriptors_1, descriptors_2, matches);// 再次计算消耗时间t2 = chrono::steady_clock::now();time_used = chrono::duration_cast<chrono::duration<double>>(t2 - t1);cout << "match ORB cost = " << time_used.count() << " seconds. " << endl;// 第四步:匹配点对筛选// 计算vector<DMatch> matches中的最小距离和最大距离// 此处使用了C++中的lambda函数[](){}auto min_max = minmax_element(matches.begin(), matches.end(), [](const DMatch &m1, const DMatch &m2) { return m1.distance < m2.distance; });double min_dist = min_max.first->distance;double max_dist = min_max.second->distance;printf("-- Max dist : %f \n", max_dist);printf("-- Min dist : %f \n", min_dist);//当描述子之间的距离大于两倍的最小距离时,即认为匹配有误.但有时候最小距离会非常小,设置一个经验值30作为下限.// 将所有符合条件的匹配放入了一个新的vector容器内std::vector<DMatch> good_matches;for (int i = 0; i < descriptors_1.rows; i++){if (matches[i].distance <= max(2 * min_dist, 30.0)){good_matches.push_back(matches[i]);}}// 第五步:绘制匹配结果Mat img_match;Mat img_goodmatch;// 绘制所有匹配drawMatches(img_1, keypoints_1, img_2, keypoints_2, matches, img_match);// 绘制筛选后的匹配drawMatches(img_1, keypoints_1, img_2, keypoints_2, good_matches, img_goodmatch);// 显示图片imshow("all matches", img_match);imshow("good matches", img_goodmatch);waitKey(0);return 0;
}

6.3 将特征数据写入磁盘/从磁盘读取特征数据并匹配输出

自己写的代码,记录一下
将图片与std::vector<cv::Point2f>类型的特征数据存储到本地:

        // 开始保存if (need_save >= 0) {if (need_save == 0) {// 将数据保存到这里// 保存图片string save_path = "存储目录";cv::imwrite( save_path + "cur_img.jpg", cur_img);cv::imwrite( save_path + "forw_img.jpg", forw_img);// 保存特征点ostringstream out1;// 将vector中的<cv::Point2f>转换为字符串储存for (auto p = cur_pts.begin(); p != cur_pts.end(); p++) {out1 << to_string((*p).x) << " " <<  to_string((*p).y) << " " << endl;}// 写入文件ofstream fout1(save_path + "cur_pts.txt");if (fout1) {//将out流转换为string类型,写入到文件流中fout1 << out1.str() << endl;fout1.close();}ostringstream out2;// 将vector中的<cv::Point2f>转换为字符串储存for (auto p = forw_pts.begin(); p != forw_pts.end(); p++) {out2 << to_string((*p).x) << " " <<  to_string((*p).y) << " " << endl;}// 写入文件ofstream fout2(save_path + "forw_pts.txt");if (fout2) {//将out流转换为string类型,写入到文件流中fout2 << out2.str() << endl;fout2.close();}}need_save -= 1;}

从磁盘读取图片与float型的特征点坐标,用ORB进行匹配并输出匹配连接图像:

#include <iostream>
#include <string>
#include <fstream>
#include <vector>#include <opencv2/opencv.hpp>using namespace std;
using namespace cv;int main(int argc, char **argv) {string save_path = "存储目录";// 从磁盘上读图片cv::Mat cur_img = cv::imread(save_path + "cur_img.jpg", 0);cv::Mat forw_img = cv::imread(save_path + "forw_img.jpg", 0);// 从磁盘上读取特征点,并以vector<cv::KeyPoint >的形式储存string temp; int pos;float x;float y;ifstream cur_pts_file(save_path + "cur_pts.txt"); // 以vector<cv::KeyPoint >的形式储存vector<cv::KeyPoint > cur_pts;if (!cur_pts_file.is_open()) { cout << "未成功打开文件cur_pts.txt" << endl; } while(getline(cur_pts_file, temp)) { if (temp.length() != 0) {// 获得字符串// 找到用于分割的空格位置pos = temp.find(" "); // x坐标x = stof(temp.substr (0, pos));// y坐标y = stof(temp.substr (pos+1, temp.length()-pos-1));// 将<cv::Point2f >转换为<cv::KeyPoint >KeyPoint temp_keypoint;temp_keypoint.pt = Point2f(x, y);// 将<cv::KeyPoint >放入vectorcur_pts.push_back(temp_keypoint);}} cur_pts_file.close(); ifstream forw_pts_file(save_path + "forw_pts.txt"); vector<cv::KeyPoint > forw_pts;if (!forw_pts_file.is_open()) { cout << "未成功打开文件forw_pts.txt" << endl; } while(getline(forw_pts_file, temp)) { if (temp.length() != 0) {// 获得字符串// 找到分割的空格位置pos = temp.find(" "); // x坐标x = stof(temp.substr (0, pos));// y坐标y = stof(temp.substr (pos+1, temp.length()-pos-1));// 将<cv::Point2f >转换为<cv::KeyPoint >KeyPoint temp_keypoint;temp_keypoint.pt = Point2f(x, y);// 将<cv::KeyPoint >放入vectorforw_pts.push_back(temp_keypoint);}} forw_pts_file.close(); // // 打印行数进行验证是否都加载正常// cout << cur_pts.size() << endl;// cout << forw_pts.size() << endl;// 创建匹配vector<DMatch> matches;BFMatcher bfMatcher(NORM_L2);//计算特征点描述子,特征向量提取Mat dst1, dst2;// 使用SIFT会产生double free or corruption (!prev) 报错// Ptr<SiftDescriptorExtractor> descriptor = SiftDescriptorExtractor::create();Ptr<DescriptorExtractor> descriptor = ORB::create();// 计算描述子descriptor->compute(cur_img, cur_pts, dst1);descriptor->compute(forw_img, forw_pts, dst2);// 进行匹配bfMatcher.match(dst1, dst2, matches);// 用于输出的图像cv::Mat out_image;// 绘制输出图像drawMatches(cur_img, cur_pts, forw_img, forw_pts, matches, out_image);// 显示输出图像imshow("连线图像", out_image);waitKey(0);return 0;
} 

7. cv::Mat与Eigen::Matrix互相转换

需要导入额外库文件

#include <opencv2/core/eigen.hpp>

互相转换

// 将cv::Mat转换为Eigen::Matrix
cv::cv2eigen(mat_cv, matrix_eigen);// 将Eigen::Matrix转换为cv::Mat
cv::eigen2cv(matrix_eigen, mat_cv);

8. 在窗口中显示放大后的图像

cv::namedWindow("show", 0);
cv::resizeWindow("show", cv::Size(1920, 1080));
cv::imshow("show", img);

C++学习笔记——opencv2模块(图像处理)相关推荐

  1. Python学习笔记:第三方模块2

    前言 最近在学习深度学习,已经跑出了几个模型,但Pyhton的基础不够扎实,因此,开始补习Python了,大家都推荐廖雪峰的课程,因此,开始了学习,但光学有没有用,还要和大家讨论一下,因此,写下这些帖 ...

  2. python中socket模块常用吗_python网络学习笔记——socket模块使用记录

    此文章记录了笔者学习python网络中socket模块的笔记. 建议初次学习socket的读者先读一遍socket模块主要函数的介绍. socket模块的介绍可以参考笔者的前一篇关于socket官方文 ...

  3. Verilog学习笔记-——Verilog模块例化

    Verilog学习笔记---Verilog模块例化 在一个模块中引用另一个模块,对其端口进行相关连接,叫做模块例化.模块例化建立了描述的层次.信号端口可以通过位置或名称关联,端口连接也必须遵循一些规则 ...

  4. B站台湾大学郭彦甫|MATLAB 学习笔记|08 图像处理I Image Processing

    MATLAB学习笔记(08 图像处理I Image Processing) 如果想获得更好浏览体验的朋友可以转到下面链接 08 1. 基本操作 读取图像 imread() 展示图像 imshow() ...

  5. B站台湾大学郭彦甫|MATLAB 学习笔记|09 图像处理II Image Processing

    MATLAB学习笔记(09 图像处理II Image Processing) 如果想获得更好浏览体验的朋友可以转到下面链接 09 1. 提出问题 题目:如何找出图片中的米粒,并且确定他们的大小? 老师 ...

  6. Python学习笔记13_模块

    Python学习笔记13_模块 文章目录 Python学习笔记13_模块 1.导入模块和的方法及使用 2.分层的文件系统中常用的包结构 3.OS 模块 4.sys 模块 5.math 模块 6.ran ...

  7. 【小猫爪】AUTOSAR学习笔记15-BswM模块

    [小猫爪]AUTOSAR学习笔记15-BswM模块 前言 1 BswM模块简介 2 BswM功能简介 2.1 模式仲裁 2.2 模式控制 END 前言   上一节介绍了一个非常夸张的EcuM模块,其中 ...

  8. 【小猫爪】AUTOSAR学习笔记14-EcuM模块

    [小猫爪]AUTOSAR学习笔记14-EcuM模块 前言 1 EcuM模块简介 2 EcuM功能简介 2.1 EcuM的状态机 2.2 RUN和POST_RUN 2.3 Startup阶段 1. St ...

  9. 数字图像处理学习笔记 六 彩色图像处理

    目录 (一)彩色模型介绍 1.1 RGB模型 1.2 CMY.CMYK模型 1.3 HSI彩色模型 1.4 HSV模型 1.5 YCbCr 彩色空间 (二)伪彩色图像处理 (三)全彩色图像处理及彩色变 ...

  10. Julia 学习笔记(五) | 模块开发 - 保姆级教程

    唠唠闲话 今年参与了 OSPP'22 的 Julia 开源项目,期间学习了 Julia 模块从开发到测试的完整过程.借这个机会,整理这段时间学习到的知识.Julia 在开发提供的工具便捷丰富,且对新人 ...

最新文章

  1. 度量.net framework 迁移到.net core的工作量
  2. 数据结构与算法(C++)– 队列(Queue)
  3. postgresql数据表增删改:使用pgadmin和SQL的方式实现
  4. I2C原理及应用实例
  5. [转]Python 列表(List) 的三种遍历(序号和值)方法
  6. 源码分析Thread
  7. 高中关于人工智能方面的课题_如何看待计算机专业开始设置人工智能课程
  8. 李宁是怎么输给安踏的?
  9. python不会英语不会数学怎么自学-零基础想自学PYTHON如果补数学怎么补?
  10. IT招聘负责人:成功简历六大要素
  11. 【转】如何理解NPV与IRR的区别??
  12. matlab 自动交易系统设计3
  13. 图书管理系统C语言课程设计
  14. Cookie简明小册
  15. 复杂网络理论及其应用-基本概念
  16. 穿越“惊世骇俗”的美景
  17. sqlserver还原数据库时失败因为 当前没有数据库备份
  18. 痞子衡嵌入式:在i.MXRT1170上启动含DQS的Octal Flash可不严格设Dummy Cycle (以MT35XU512为例)...
  19. info There appears to be trouble with your network connection. Retrying...
  20. 工业云平台大数据统计分析有什么优势?

热门文章

  1. bios升级 acer linux,《原创》Acer更新BIOS的问题,傻瓜都会
  2. 激活windows 7 RTM方法
  3. 图网络模型原理详解(Graph Network)
  4. CSND Markdown模板
  5. Elasticsearch:使用 Python 进行 Bulk insert 及 Scan
  6. 孙钟秀-《 操作系统教程 》(第4版)注释(稿)
  7. 扩散模型——下一个图像生成热点,快上车!!!
  8. android模拟器登录用户,如何在模拟器上登录小米账号的游戏
  9. 如何学习嵌入式Linux_韦东山
  10. 2015年江苏对口单招计算机试卷答案,2016江苏对口单招试卷 2015年江苏对口单招计算机试卷.doc...