小言_互联网的博客

自动调整图片方向并划窗剪裁

233人阅读  评论(0)

最近在“2021广东工业智造创新大赛 智能算法赛:瓷砖表面瑕疵质检” 中遇到一些图片,有不同角度偏差。类似卫星图,分辨率特别大,目标却特别小,这就需要对原始图片自动调整角度,划窗剪裁和相应的坐标映射。

读取图片

对于大图片来说,直接使用cv2.imread会比PIL再转numpy array慢 30% 左右,这里推荐使用Image.open读取。


   
  1. import numpy as np
  2. import cv2
  3. from PIL import Image
  4. # org_img = cv2.imread(BASE_DIR + img_file)
  5. org_img = Image.open(BASE_DIR + img_file)
  6. org_img = cv2.cvtColor(np.asarray(org_img), cv2.COLOR_RGB2BGR)

检测外框

1. 转灰度图


   
  1. # 灰度图
  2. greyPic = cv2.cvtColor(org_img, cv2.COLOR_BGR2GRAY)

2. 对图像进行二值化操作

这里阈值采用平均像数值,可满足大多数场景,特殊场合下可以自己调整。


   
  1. # threshold(src, thresh, maxval, type, dst=None)
  2. # src是输入数组,thresh是阈值的具体值,maxval是 type取THRESH_BINARY或者THRESH_BINARY_INV时的最大值
  3. # type5种类型,这里取 0:THRESH_BINARY ,当前点值大于阈值时,取maxval,也就是前一个参数,否则设为 0
  4. # 该函数第一个返回值是阈值的值,第二个是阈值化后的图像
  5. ret, binPic = cv2.threshold(greyPic, greyPic.mean(), 255, cv2.THRESH_BINARY)

3. 中值滤波

median = cv2.medianBlur(binPic, 5)

4. 找出轮廓


   
  1. # findContours()有三个参数:输入图像,层次类型和轮廓逼近方法
  2. # 该函数会修改原图像,建议使用img. copy()作为输入
  3. # 由函数返回的层次树很重要,cv2.RETR_TREE会得到图像中轮廓的整体层次结构,以此来建立轮廓之间的‘关系 '。
  4. # 如果只想得到最外面的轮廓,可以使用cv2.RETE_EXTERNAL。这样可以消除轮廓中其他的轮廓,也就是最大的集合
  5. # 该函数有三个返回值:修改后的图像,图像的轮廓,它们的层次
  6. contours, hierarchy = cv2.findContours(median, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_SIMPLE)

5. 获取最小外接矩形


   
  1. maxArea = 0
  2. # 挨个检查看那个轮廓面积最大
  3. for i in range( len(contours)):
  4. if cv2.contourArea(contours[i]) > cv2.contourArea(contours[maxArea]):
  5. maxArea = i
  6. hull = cv2.convexHull(contours[maxArea])
  7. hull = np.squeeze(hull)
  8. # 得到最小外接矩形的(中心(x,y), (宽,高), 旋转角度)
  9. rect = cv2.minAreaRect(hull)
  10. # 通过box会出矩形框
  11. box = np.int0(cv2.boxPoints(rect))

调整图片角度

获取角度偏差,计算仿射矩阵,将外接矩形box的坐标进行变换。


   
  1. center = rect[ 0]
  2. angle = rect[ 2]
  3. if angle > 45:
  4. angle = angle - 90
  5. # 旋转矩阵
  6. M = cv2.getRotationMatrix2D(center, angle, 1)
  7. h, w, c = org_img.shape
  8. # 旋转图片
  9. dst = cv2.warpAffine(org_img, M, (w, h))
  10. # 坐标变换
  11. poly_r = np.asarray([(M[ 0][ 0] * x + M[ 0][ 1] * y + M[ 0][ 2],
  12. M[ 1][ 0] * x + M[ 1][ 1] * y + M[ 1][ 2]) for (x, y) in box])

裁剪图片


   
  1. x_s, y_s = np.int0(poly_r.min(axis= 0))
  2. x_e, y_e = np.int0(poly_r.max(axis= 0))
  3. # 设置预留边框
  4. border = 100
  5. x_s = int(max((x_s - border), 0))
  6. y_s = int(max((y_s - border), 0))
  7. x_e = int(min((x_e + border), w))
  8. y_e = int(min((y_e + border), h))
  9. # 剪裁
  10. cut_img = dst[y_s:y_e, x_s:x_e, :]

划窗分割

图片已经扶正后,就可以根据需要进行划窗分割。指定划窗大小,重叠比例和输出目录后,就能获取一堆小图片了。


   
  1. def slice(img, img_file, window_l= 1024, overlap= 0.2, out_dir= ""):
  2. # 切割图片 生成文件 xxx_000_000.jpg
  3. h, w, c = img.shape
  4. step_l = int(window_l - window_l * overlap) # 步长
  5. x_num = int(np.ceil(max((w - window_l) / step_l, 0))) + 1
  6. y_num = int(np.ceil(max((h - window_l) / step_l, 0))) + 1
  7. for i in range(x_num):
  8. for j in range(y_num):
  9. x_s, x_e = i * step_l, i * step_l + window_l
  10. y_s, y_e = j * step_l, j * step_l + window_l
  11. # 修正越界
  12. if x_e > w:
  13. x_s, x_e = w - window_l, w
  14. if y_e > h:
  15. y_s, y_e = h - window_l, h
  16. assert w >= window_l
  17. assert h >= window_l
  18. new_img_file = img_file[: -4] + '_%03d_%03d.jpg' % (i, j)
  19. im = img[y_s:y_e, x_s:x_e, :]
  20. cv2.imwrite(out_dir + new_img_file, im)
  21. return

批量化处理

封装一下函数,对整个目录扫描,并保存和原图的对应关系配置文件,为之后还原坐标做准备。


   
  1. def adjust_angle(org_img, img_file, border= 100):
  2. h, w, c = org_img.shape
  3. # 统一尺度,如果尺寸小于 4000,放大一倍
  4. scale = 1
  5. if w < 4000 or h < 4000:
  6. scale = 2
  7. w = int(w * scale)
  8. h = int(h * scale)
  9. org_img = cv2.resize(org_img, (w, h), interpolation=cv2.INTER_LINEAR)
  10. x_s, y_s, x_e, y_e, rect, new_img = getCornerPoint(org_img)
  11. # 去除边框
  12. x_s = int(max((x_s - border), 0))
  13. y_s = int(max((y_s - border), 0))
  14. x_e = int(min((x_e + border), w))
  15. y_e = int(min((y_e + border), h))
  16. img = new_img[y_s:y_e, x_s:x_e, :]
  17. data = dict()
  18. data[ 'name'] = img_file
  19. data[ 'xyxy'] = [x_s, y_s, x_e, y_e]
  20. data[ 'rect'] = rect
  21. data[ 'border'] = border
  22. data[ 'scale'] = scale
  23. return data, img

设置BASE_DIR为原图目录,OUT_ADJUST为角度调整后目录,adjust.json为配置文件。


   
  1. result_json = []
  2. img_list = os.listdir(BASE_DIR)
  3. for img_file in tqdm(img_list):
  4. org_img = Image.open(BASE_DIR + img_file)
  5. org_img = cv2.cvtColor(np.asarray(org_img), cv2.COLOR_RGB2BGR)
  6. data, img = adjust_angle(org_img, img_file, border= 100)
  7. result_json. append(data)
  8. cv2.imwrite(OUT_ADJUST + img_file, img)
  9. slice(img, img_file, TARGET, overlap=OVERLAP, out_dir=OUT_SLICE)
  10. with open(OUT_DIR + 'adjust.json', 'w') as fp:
  11. json.dump(result_json, fp, indent= 4, ensure_ascii=False)

坐标还原

1. 读取切片图片列表


   
  1. with open( "instances_test2017_1024.json", 'r') as f:
  2. test_imgs = json.load(f)[ 'images']
  3. test_imgs_dict = {}
  4. for i, obj in enumerate(test_imgs):
  5. img_name = obj[ 'file_name']
  6. test_imgs_dict[img_name] = i

2. 读取原始文件信息


   
  1. with open(OUT_DIR + 'adjust.json', 'r') as fp:
  2. img_info = json.load(fp)
  3. img_info_dict = {}
  4. for i, obj in enumerate(img_info):
  5. img_name = obj[ 'name']
  6. img_info_dict[img_name] = i

3. 读取推理结果文件

将一堆子图的推理结果放到一起,可以充分利用mmdetection的多线程DataLoader和大显存的batch size来加速推理过程。


   
  1. with open( "result_1024-20.pkl", 'rb') as f:
  2. pred_set = pickle.load(f)

4. 合并坐标到角度调整图

获取到图片长宽,根据相同的划窗参数,可以还原出每个子图的基准坐标x_sy_s

其中test_imgs_dict中保存了子图的文件名字典,pred_set保存了预测结果列表。通过形如XXX_000_000.jpg的文件名,经过两级映射后就能获取到对应的推理结果集了。


   
  1. def merge_result(info, pred_set, test_imgs_dict, img_file, window_l= 1024, overlap= 0.2):
  2. assert info[ 'name'] == img_file
  3. # 这里只需要取图片长宽信息,避免读图操作太慢,直接读取配置文件
  4. x1, y1, x2, y2 = info[ 'xyxy']
  5. w = x2 - x1
  6. h = y2 - y1
  7. step_l = int(window_l - window_l * overlap) # 步长
  8. x_num = int(np.ceil(max((w - window_l) / step_l, 0))) + 1
  9. y_num = int(np.ceil(max((h - window_l) / step_l, 0))) + 1
  10. result = [np.array([[], ] * 5).T.astype(np. float32), ] * 6 # 分类数为 6, bbox.shape 为( 0, 5)
  11. for i in range(x_num):
  12. for j in range(y_num):
  13. x_s, x_e = i * step_l, i * step_l + window_l
  14. y_s, y_e = j * step_l, j * step_l + window_l
  15. # 修正越界
  16. if x_e > w:
  17. x_s, x_e = w - window_l, w
  18. if y_e > h:
  19. y_s, y_e = h - window_l, h
  20. assert w >= window_l
  21. assert h >= window_l
  22. new_img_file = img_file[: -4] + '_%03d_%03d.jpg' % (i, j)
  23. pred = pred_set[test_imgs_dict[new_img_file]] # 获取预测结果
  24. for label_id, bboxes in enumerate(pred):
  25. # 坐标修正 x_s, y_s 划窗基坐标
  26. bboxes[:, 0] = bboxes[:, 0] + x_s
  27. bboxes[:, 1] = bboxes[:, 1] + y_s
  28. bboxes[:, 2] = bboxes[:, 2] + x_s
  29. bboxes[:, 3] = bboxes[:, 3] + y_s
  30. # 合并到大图
  31. result[label_id] = np.vstack((result[label_id], bboxes))
  32. return result

5. 坐标映射到原图

首先获取到原图信息info,获取外接矩形参数、旋转角度、缩放比例、边框大小等等,构建一个逆仿射矩阵M,对所有检测框进行坐标变换。


   
  1. def generate_json(pred, info, img_file, score_threshold= 0.05, out_dir= "", vis=False):
  2. base_x, base_y, x2, y2 = info[ 'xyxy']
  3. rect = info[ 'rect']
  4. scale = info[ 'scale']
  5. border = info[ 'border']
  6. x1, y1, x2, y2 = (border, border, x2 - border, y2 - border)
  7. poly = np.asarray([(x1, y1), (x2, y1), (x2, y2), (x1, y2)])
  8. center = tuple(rect[ 0])
  9. angle = rect[ 2]
  10. if angle > 45:
  11. angle = angle - 90
  12. # 逆旋转还原
  13. M = cv2.getRotationMatrix2D(center, -angle, 1)
  14. # 遍历完所有分片, nms
  15. json_results = []
  16. for label_id, bboxes in enumerate(pred): # 6个分类
  17. bboxes = nms(np.array(bboxes[:, : 4]), np.array(bboxes[:, 4]), iou_threshold= 0.5)[ 0]
  18. # 坐标转换到原始图片
  19. bboxes[:, 0] = bboxes[:, 0] + base_x
  20. bboxes[:, 1] = bboxes[:, 1] + base_y
  21. bboxes[:, 2] = bboxes[:, 2] + base_x
  22. bboxes[:, 3] = bboxes[:, 3] + base_y
  23. for ann in bboxes:
  24. x1, y1, x2, y2, score = ann
  25. if score < score_threshold:
  26. continue
  27. poly_r = np.asarray([(M[ 0][ 0] * x + M[ 0][ 1] * y + M[ 0][ 2],
  28. M[ 1][ 0] * x + M[ 1][ 1] * y + M[ 1][ 2]) for (x, y) in
  29. [(x1, y1), (x1, y2), (x2, y1), (x2, y2)]])
  30. # 还原小图片缩放
  31. ann = poly2ann(poly_r, score, scale=scale)
  32. data = dict()
  33. data[ 'name'] = img_file
  34. data[ 'category'] = label_id + 1
  35. data[ 'bbox'] = [float(ann[ 0]), float(ann[ 1]), float(ann[ 2]), float(ann[ 3])]
  36. data[ 'score'] = float(score)
  37. json_results. append(data)
  38. return json_results

最后经过nms和一系列后处理之后,映射到原图上即可。

完美收工!


转载:https://blog.csdn.net/weixin_47479625/article/details/113449495
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场