孩子的数字图像处理的课程大作业。题目要求:
- 1. 拍摄一张带有自己电脑屏幕的画质清晰的照片
- 2. 使用合适的图像处理方法准确识别出照片中的电脑屏幕
- 3. 用另外一张图片替换图像中的电脑屏幕
针对难点一: 拟采用边缘检测检测像素突变点提取边缘线条+轮廓提取+拟合外接四边形寻找轮廓中满足面积条件的四边形。考虑到拍摄图像有很多噪点干扰,采用中值滤波进行平滑处理,过滤椒盐噪声。设定矩形区域面积阈值,去除不满足条件轮廓,对轮廓进行多边形拟合迭代,直到拟合成四边形,并排序输出四个点坐标。
针对难点二: 拟采用投影变换,利用getPerspectiveTransform函数求出变换矩阵M,再用M将代替换图像投影变换到矩形区域,其余面积像素为0,以便后续图片融合相加。
1.算法流程图
# 开始图像处理,读取图片文件 image = cv2.imread(Config.src) image_copy = image.copy() image_mix = cv2.imread(Config.drc) cv2.imshow("image", image) cv2.imshow("image_mix", image_mix) #获取原始\目标图像的大小 srcHeight,srcWidth ,channels = image.shape drcHeight,drcWidth ,channels_drc = image_mix.shape # 镶嵌的图的四个顶点 box_drc=np.array([[0, 0],[drcWidth, 0],[drcWidth, drcHeight],[0, drcHeight]]) dst_rect = np.float32([box_drc[0], box_drc[1], box_drc[2], box_drc[3]]) #转成灰度图 gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) # 中值滤波平滑,消除噪声 # 当图片缩小后,中值滤波的孔径也要相应的缩小,否则会将有效的轮廓擦除 binary = cv2.medianBlur(gray,Config.medianBlur_value) #转换为二值图像 ret, binary = cv2.threshold(binary, Config.threshold_thresh, 255, cv2.THRESH_BINARY) cv2.imshow("binary", binary)
# canny 边缘检测 binary = cv2.Canny(binary, Config.canny_minvalue, Config.canny_maxvalue, apertureSize = Config.canny_apertureSize) #显示边缘检测的结果 cv2.imshow("Canny", binary) # 提取轮廓 contours,_ = cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) ## 输出轮廓数目 #print("the count of contours is %d \n"%(len(contours)))
对轮廓拟合外接四边形,寻找矩形坐标:
# 找出外接四边形, c是轮廓的坐标数组 def boundingBox(idx,c): if len(c) < Config.min_contours: return None epsilon = Config.epsilon_start while True: approxBox = cv2.approxPolyDP(c,epsilon,True) #求出拟合得到的多边形的面积 theArea = math.fabs(cv2.contourArea(approxBox)) #输出拟合信息 print("contour idx: %d ,contour_len: %d ,epsilon: %d ,approx_len: %d ,approx_area: %s"%(idx,len(c),epsilon,len(approxBox),theArea)) if (len(approxBox) < 4): return None if theArea > Config.min_area: if (len(approxBox) > 4): # epsilon 增长一个步长值 epsilon += Config.epsilon_step continue else: #approx的长度为4,表明已经拟合成矩形了 #转换成4*2的数组 approxBox = approxBox.reshape((4, 2)) return approxBox else: print("failed to find boundingBox,idx = %d area=%f"%(idx, theArea)) return None #针对每个轮廓,拟合外接四边形,如果成功,则将记录该四个点 for idx,c in enumerate(contours): approxBox = boundingBox(idx,c) if approxBox is None: # print("\n") continue # 获取最小矩形包络 rect = cv2.minAreaRect(approxBox) box = cv2.boxPoints(rect) box = np.int0(box) box = box.reshape(4,2) box = order_points(box) print("boundingBox:\n",box)
由于得到的四边形的四个点并不是与待替换图形的四个点是上下左右一一对应的,所以要对得到的四个点进行排序,查明各点归属位置(利用左上角的点横纵坐标和最小,而右下角的和最大,右上角的差异最小,而左下角的差异最大)。
#对坐标点进行排序 def order_points(pts): # 初始化将要排序的坐标列表 # 所以列表中的第一个条目是左上角, # 第二条是右上角,第三条是右下角,第四条是左下角 rect = np.zeros((4, 2), dtype="float32") # 左上角的和最小,而右下角的和最大 s = pts.sum(axis=1) rect[0] = pts[np.argmin(s)] rect[2] = pts[np.argmax(s)] # 计算两个点之间的差异,右上角的差异最小,而左下角的差异最大 diff = np.diff(pts, axis=1) rect[1] = pts[np.argmin(diff)] rect[3] = pts[np.argmax(diff)] # [top-left, top-right, bottom-right, bottom-left] return rect
得到电脑屏幕到代替换图像的投影变换矩阵M:
# 生成透视变换矩阵,透视变换 rect = np.array([[0, 0],[w - 1, 0],[w - 1, h - 1],[0, h - 1]],dtype="float32") M = cv2.getPerspectiveTransform(src_rect, rect) #得到透视变换后的图像,即抓取到图像 warped = cv2.warpPerspective(image_copy, M, (w, h)) #w和h为 cv2.imshow("warped",warped) cv2.imwrite('picture_101.jpg',warped) print("\n")
根据变换矩阵M求取反变换矩阵,然后利用掩膜变换、图像拼接等步骤将代替换图像替换到电脑屏幕区域。
#反变换矩阵 M = cv2.getPerspectiveTransform(dst_rect, src_rect) result_img_22 = cv2.warpPerspective(image_mix, M, (srcWidth, srcHeight)) #对目标进行镶嵌 img2gray = cv2.cvtColor(result_img_22,cv2.COLOR_BGR2GRAY) ret, mask = cv2.threshold(img2gray, 0, 255, cv2.THRESH_BINARY) mask_inv = cv2.bitwise_not(mask) cv2.imshow('mask',mask_inv) img1_bg = cv2.bitwise_and(image,image,mask = mask_inv) cv2.imshow('img1_bg',img1_bg) img2_fg = cv2.bitwise_and(result_img_22,result_img_22,mask = mask) dst = cv2.add(img1_bg,img2_fg) cv2.imshow('result',dst) cv2.imwrite('result.jpg',dst) print('over') cv2.waitKey(0) cv2.destroyAllWindows()