最右、直方图均衡化等流程
发布时间:2025-06-24 19:36:50 作者:北方职教升学中心 阅读量:625
原图像:

V通道图像:

对比度增强
通过顶帽变换来实现对比度增强。
|
利用opencv实现车牌检测
整体流程涉及5个部分
- 图像通道转换
- 对比度增强
- 边缘连接
- 二值化
- 边界区域裁剪
图像通道转换
将RGB图像转换为HSV图像,仅保留V通道。漏检的车牌本身边缘不清晰,检测难度较大
消融实验
方法 | 最终图像检测框 | 车牌检测数量 |
---|---|---|
最终方法 | ![]() | 40 |
去掉对比度增强 | ![]() | 39 |
去掉边缘连接 | ![]() | 39 |
内边界面积过滤阈值4000 | ![]() | 38 |
内边界面积过滤阈值5000 | ![]() | 38 |
代码
"""主要的步骤为:1)提取单通道图片,选项为 (灰度图片/HSV中的value分支)2)提升对比度,选项为 (形态学中的顶帽/灰度拉伸)3)边缘连接(膨胀)4)二值化5)利用findcontours函数找到边缘6)裁剪图片,车牌图片存储7) 对车牌预处理8)方向矫正9)车牌精确区域搜索10) 字符分割11) 字符识别"""import cv2import copyimport numpy as npimport mathimport osdef SingleChannel(img) : """ 用于车牌检测 得到单通道图片,主要测试两种方式,灰度通道以及hsv中的v通道 :param img: 输入图片 :return: """ hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV) hue, saturation, value = cv2.split(hsv) cv2.imshow("SingleChannel", value) return valuedef Contrast(img) : """ 用于车牌检测 利用tophat,提高图片对比度, :param img: 输入图片 :return: """ kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3)) # applying topHat/blackHat operations topHat = cv2.morphologyEx(img, cv2.MORPH_TOPHAT, kernel) cv2.imshow("tophat", topHat) blackHat = cv2.morphologyEx(img, cv2.MORPH_BLACKHAT, kernel) cv2.imshow("blackhat", blackHat) add = cv2.add(img, topHat) subtract = cv2.subtract(add, blackHat) cv2.imshow('Constrast', subtract) return subtractdef threshold(img) : """ 用于车牌检测 采用cv2.adaptiveThreshold方法,对图片二值化 :param img: 输入图像 :return: """ thresh = cv2.adaptiveThreshold(img, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY_INV, 19, 9) cv2.imshow("thresh", thresh) return threshglobal crop_numcrop_num = 0def drawCoutrous(img_temp) : """ 对输入图像查找内边缘,设置阈值,去除一些面积较小的内边缘 :param img_temp: 输入图像,经过预处理 :return: """ threshline = 2000 imgCopy = copy.deepcopy(img_temp) contours, hierarchy = cv2.findContours(imgCopy, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_SIMPLE) # print(len(contours), contours[0].shape) # print(hierarchy.shape) maxarea = 0 conid = 0 img_zero = np.zeros(img.shape) # print("img_zero.shape is : ",img_zero.shape) num_contours = 0 contoursList = [] for i in range(len(contours)) : if hierarchy[0][i][3] >= 0 : temparea = math.fabs(cv2.contourArea(contours[i])) # print(math.fabs(cv2.contourArea(contours[i]))) if temparea > maxarea : conid = i maxarea = temparea if temparea > threshline : num_contours += 1 if num_contours % 7 == 0 : cv2.drawContours(img_zero, contours, i, (0,0,255),1) if num_contours % 7 == 1 : cv2.drawContours(img_zero, contours, i, (255,0,0),1) if num_contours % 7 == 2 : cv2.drawContours(img_zero, contours, i, (0,255,0),1) if num_contours % 7 == 3 : cv2.drawContours(img_zero, contours, i, (0,255,255),1) if num_contours % 7 == 4 : cv2.drawContours(img_zero, contours, i, (255,0,255),1) if num_contours % 7 == 5 : cv2.drawContours(img_zero, contours, i, (255,255,0),1) if num_contours % 7 == 6: cv2.drawContours(img_zero, contours, i, (255, 255, 255), 1) # print(contours[i].shape) contoursList.append(contours[i]) # print("maxarea: ",maxarea) # print("number of contours is ", num_contours) # cv2.drawContours(img_zero, contours, conid, (0, 0, 255), 1) cv2.imshow("with contours",img_zero) return contoursListdef DrawRectangle(img, img_temp, ConList) : """ 得到车牌边缘的的x,y坐标最小最大值,再原图上绘制bounding box,得到裁剪后的车牌图像 :param img: 原图 :param img_temp: 二值图像 :param ConList: 图像的边缘轮廓 :return: null """ length = len(ConList) rectanglePoint = np.zeros((length, 4, 1, 2), dtype = np.int32) img_zeros = np.zeros(img_temp.shape) img_copy = copy.deepcopy(img) img_copy_1 = copy.deepcopy(img) # print("img_zeros, length; ", img_zeros.shape, length) for i in range(length) : contours = ConList[i] minx, maxx, miny, maxy = 1e6, 0, 1e6, 0 for index_num in range(contours.shape[0]) : if contours[index_num][0][0] < minx : minx = contours[index_num][0][0] if contours[index_num][0][0] > maxx : maxx = contours[index_num][0][0] if contours[index_num][0][1] < miny : miny = contours[index_num][0][1] if contours[index_num][0][1] > maxy : maxy = contours[index_num][0][1] # print(minx, maxx, miny, maxy) rectanglePoint[i][0][0][0], rectanglePoint[i][0][0][1] = minx, miny rectanglePoint[i][1][0][0], rectanglePoint[i][1][0][1] = minx, maxy rectanglePoint[i][2][0][0], rectanglePoint[i][2][0][1] = maxx, maxy rectanglePoint[i][3][0][0], rectanglePoint[i][3][0][1] = maxx, miny # rectanglePoint.dtype = np.int32 # print(rectanglePoint[i].shape) crop_save(minx, maxx, miny, maxy, img_copy_1) # print("dx: ",maxx-minx,"dy: ",maxy-miny, "area: ", (maxx-minx)*(maxy-miny)) cv2.polylines(img_copy, [rectanglePoint[i]], True, (0,0,255),2) cv2.imshow("img_zeros_haha", img_copy)def crop_save(minx, maxx, miny, maxy, img_original) : """ 裁剪原图,根据minx,maxx,miny,maxy :param minx: x坐标最小值 :param maxx: x坐标最大值 :param miny: y坐标最小值 :param maxy: y坐标最大值 :param img_original: 由于需要将绘制结果再原图中显示,输入原图 :return: """ global crop_num epsx = 60 epsy = 30 dx = maxx - minx dy = maxy - miny if dx == dy : return if dx >= 600 - epsx : dx1, dx2, dx3, dx4 = minx, minx + 1 * int(dx / 3), minx + 2 * int(dx / 3), maxx save_pth = './crop40/cropimg_' + str(crop_num) + '.jpg' # cv2.imwrite(save_pth, img_original[dx1:dx2, miny:maxy,:]) cv2.imwrite(save_pth, img_original[miny:maxy, dx1:dx2, :]) crop_num += 1 save_pth = './crop40/cropimg_' + str(crop_num) + '.jpg' cv2.imwrite(save_pth, img_original[miny:maxy, dx2:dx3, :]) crop_num += 1 save_pth = './crop40/cropimg_' + str(crop_num) + '.jpg' cv2.imwrite(save_pth, img_original[miny:maxy, dx3:dx4, :]) crop_num += 1 elif dx >= 400 - epsx : dx1, dx2, dx3 = minx, minx + 1 * int(dx / 2), maxx save_pth = './crop40/cropimg_' + str(crop_num) + '.jpg' cv2.imwrite(save_pth, img_original[miny:maxy, dx1:dx2, :]) crop_num += 1 save_pth = './crop40/cropimg_' + str(crop_num) + '.jpg' cv2.imwrite(save_pth, img_original[miny:maxy, dx2:dx3, :]) crop_num += 1 elif dy >= 240 - epsy : dy1, dy2, dy3, dy4 = miny, miny + 1 * int(dy / 3), miny + 2 * int(dy / 3), maxy save_pth = './crop40/cropimg_' + str(crop_num) + '.jpg' cv2.imwrite(save_pth, img_original[dy1: dy2, minx:maxx, :]) crop_num += 1 save_pth = './crop40/cropimg_' + str(crop_num) + '.jpg' cv2.imwrite(save_pth, img_original[dy2: dy3, minx:maxx, :]) crop_num += 1 save_pth = './crop40/cropimg_' + str(crop_num) + '.jpg' cv2.imwrite(save_pth, img_original[dy3: dy4, minx:maxx, :]) crop_num += 1 elif dy >= 160 - epsy : dy1, dy2, dy3 = miny, miny + 1 * int(dy / 2), maxy save_pth = './crop40/cropimg_' + str(crop_num) + '.jpg' cv2.imwrite(save_pth, img_original[dy1: dy2, minx:maxx, :]) crop_num += 1 save_pth = './crop40/cropimg_' + str(crop_num) + '.jpg' cv2.imwrite(save_pth, img_original[dy2: dy3, minx:maxx, :]) crop_num += 1 elif dx <= 200 + epsx : dx1, dx2 = minx, maxx save_pth = './crop40/cropimg_' + str(crop_num) + '.jpg' cv2.imwrite(save_pth, img_original[miny:maxy, dx1:dx2, :]) crop_num += 1 else : passif __name__ == '__main__' : pth = 'License_plates.jpg' img = cv2.imread(pth) img = cv2.resize(img, (292 * 4, 173 * 4)) cv2.imshow("original",img) # 1)提取单通道图片,选项为 (灰度图片/HSV中的value分支) singlechannel_img = SingleChannel(img) # 2)提升对比度 contrast_img = Contrast(singlechannel_img) # contrast_img = singlechannel_img # 3)边缘连接(膨胀) kernel = np.ones((2, 2), np.uint8) dilation_img = cv2.dilate(contrast_img, kernel, iterations=1) cv2.imshow("dilate", dilation_img) # dilation_img = contrast_img # 4) 二值化 threshold_img = threshold(dilation_img) # 5)利用findcontours函数找到边缘 contoursList = drawCoutrous(threshold_img) # 6) 裁剪图片,车牌图片存储 DrawRectangle(img, threshold_img, contoursList) cv2.waitKey() cv2.destroyAllWindows()
边缘连接
增强对比度后,很多车牌边缘不连续,例如
需要通过膨胀操作(Dilation Operation)来扩展边缘,实现边缘连接的目的。高度均匀分割。顶帽变换用于提取图像的小区域和局部细节。下图是检测出的内边界
对内边界进行阈值判断处理,过滤掉明显错误的情况。最上、
添加膨胀操作后,图像转变为:
二值化
将单通道V图像转换为二值图像,具体策略为Adaptive thresholding
边界区域裁剪
- 首先,利用cv2.findContours检测边界,并且获得边界的层级(hierarchy)。V通道表示颜色的明暗,常用于图像对比度拉伸、高度比较大。例如过滤面积小于2000的内边界(具体数值需要按照实际情况来定)
- 对于每个内边界,计算外接最小的矩形(可以通过统计边界内最左、白顶帽变换用于提取图像中比周围环境亮的小物体或细节;黑顶帽变换用于提取图像中比周围环境暗的小物体或细节。对于这种情况,需要对检测框按照宽度、例如
最终,整张图有41个车牌,通过上述方法,检测到了40个车牌,效果不错。
白顶帽变换:
黑顶帽变化:
通过白顶帽、以下是一个高度过大的例子,需按高度均分
