opencv之形状检测初试:黑色背景下的小红点识别

opencv在我的接触和体会中,给我更多的感受不是有多难,而是有多杂
因为其中比较复杂的算法,已经被大佬写好封装好了,我们需要做的就是调用API,然后进行参数的调整。

关键点就在这里了,因为算法它具有普适性,不可能完美契合每一个业务本身的需求,而以我们自身的水平去写算法显然不太现实,我们更多是去理解,理解怎么去用,怎么去选。而后再递进到怎么去理解,这个算法的内核。
这是我这个机器视觉小白在实践中总结得到的经验,我总是认为:做一个东西,不应只局限于这一项,而是考虑是否“举一反三”,也就是代码中的常说的可移植性,而事后总结,才有提升认知,把握全局技术的先进性。

好,方法论讲完了,现在应该是面对具体的应用场景了,我面对的业务场景是:
需要对密封盒子的黑暗环境下,对分布密集均匀但亮度不均匀的小红点进行检测,输出位置坐标和形状大小等数据。其实就是我们team在打比赛分配给我的图像处理部分的任务。

黑暗环境,小红点均匀分布,看起来很完美对吧,一个提供稳定环境,一个提供规则形状,但是在实际的工程中,我很快发现问题:


首先,在我对opencv不太了解的情况下,我动用了神秘力量(GPT)急功近利地想快点实现需求,但,从结果来看,chatgpt显然不适合全盘让他写这种实践性过强的代码,因为具体的参数不是一两句就可以描述清楚的,需要自己去调试,去看看别人成功的案例才是应对这种问题的最好方法,而gpt起到的作用更多的是让我更好的理解代码,虽然他对这类写代码能力欠佳,但是他的解读还是相当靠谱的,可以说节省了大量的学习成本,所以这也有一个结论:gpt始终是也只能是辅助,外在的东西再强也比不过自身硬。

话说回来,一开始GPT给我的方案,那是啥技术都恨不得全用上,把一张图片给他处理得明明白白,什么均衡直方,高斯模糊,白平衡和曝光,Canny边缘算法,霍夫检测圆变换,事实证明,多不代表好,少却高效才是真的有东西。这一块处理的图:

后面,我上CSDN查找了诸位大佬的代码,取了一个小段达到了目的:阈值二值化
该算法通过仅仅设置颜色阈值来实现了目的,并且效果较为良好,先来说说为啥选他,因为环境和自身的条件,只有两种颜色供我们需要检测,而阈值二值化完美的契合我们的需求,它可以将设置阈值范围内的区域变为白色,其他的就是黑色:

之后,我就在荡资料的路上一去不复返,皇天不负有心人,我找到一个与我的业务需求较为相似的:检测密集且分布较为均匀的小圆点并画出他们的轮廓,巧了,他也是用的基于阈值二值化的方法做后面的处理的,柳暗花明又一村,我急忙查看他后面的技术点,它对圆的检测是通过使用了OpenCV中的SimpleBlobDetector类来检测这些斑点,也就是圆形轮廓。该类基于图像中的亮度变化和连通性来检测斑点,通过创建一个斑点检测器和对应来实现对小圆点的检测和筛选特定圆的功能,而这些点也被叫做关键点,之后再通过遍历关键点的列表来实现将检测到的圆点画在原始图像上的功能。

事情到这里,基本已经解决大半了,但是我上手一测试,发现圆不对劲,按理来说,他应该圈住的是被我设置好阈值以上的白色区域才对,但是我发现圈中的基本上都是黑白交界,或者说其实他想圈的是黑色(后面才想到这层),到这里,我们就需要进行问题的定位了,首先,肯定不是前面阈值二值化的问题,我们通过imshow函数已经将这个照片展示出来,可以发现,效果还行,那就是检测或者画的问题,好在这里我们由好帮手,GPT小老弟闪亮登场,一出场就排除了画的嫌疑,因为画在原始图像上是通过遍历来实现的,意思就是说,上梁不正下梁歪,检测给到的东西有问题,这小子,可算给我逮到了。
我思前想后,发现参数好像没啥问题,因为我需要检测的圆比他画出来的圆要小,意思就是说他肯定是可以检测那种大小的圆形的。怎么办,又要卡住了吗?我百思不得其解,这个时候,就不得不提起一句名言:“不是读书没用,而是你读的书没用”,问一下各位读者,你读过的书用了吗?孟德尔先生在生物课本中现身说法,告诉我们控制变量法确实香,我拍摄大佬用于检测的图片发现是可行的,大佬的照片的效果奇好,然后我发现他们有一个有意思的共同点,画出来的圆区域内都是黑色,真相大白,检测的是黑色的圆的部分,这个时候,再让GPT小老弟打一下工,把那一行小小的不起眼的代码中再加一点点东西:_,num_img = cv2.threshold(img,55,255,cv2.THRESH_BINARY)改成_,num_img = cv2.threshold(img,55,255,cv2.THRESH_BINARY_INV)

阈值化的白色,黑色反转一下,问题解决了:(PS:因为是截图,所以红色的轮廓没有很明显)

 下面是经过修改整合过后的全部代码:

注:是在树莓派上调用摄像头实现
 

#一

import cv2

import numpy as np

image_path='/home/pi/photo/image.jpg'

#

class circle:

    def __init__(self,param1,param2,param3):

        self.X_location=param1

        self.Y_location=param2

        self.radius=param3

   

    def get_original(image_path):

        # 初始化摄像头

        cap = cv2.VideoCapture(0)

       

        # 检查摄像头是否成功打开

        if not cap.isOpened():

            print("无法打开摄像头")

            return

       

        while True:

            # 捕获图像

            ret, frame = cap.read()

            height,width,channels = frame.shape

            # 检查图像是否成功捕获

            if not ret:

                print("无法捕获图像")

                break

           

            # 自动曝光和白平衡

            #frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)  # 转为RGB格式

            #balance_frame = cv2.xphoto.createSimpleWB().balanceWhite(frame)

            # 显示图像

           

            cv2.imshow("Camera", frame)

           

            # 按下 'q' 键结束拍摄

            if cv2.waitKey(1) & 0xFF == ord('q'):

                # 存储图像

                cv2.imwrite(image_path, frame)

                break

       

        # 释放摄像头

        cap.release()

        print(height)

        print(width)

        print(channels)

        # 销毁窗口

        cv2.destroyAllWindows()

        print("图像已保存至", image_path)

    '''def find_rect(img_path):

        img=cv2.imread(img_path)

        #edge =cv2.Canny(img,100,250);

        ret, img = cv2.threshold(img, 80, 255, cv2.THRESH_OTSU)

        kernel = np.ones([3,3])

        img   = cv2.erode(img, kernel, iterations = 1)

        #binaly_img   = cv2.dilate(edge, kernel, iterations = 1)

        edge =cv2.Canny(img,100,250);

        contours = cv2.findContours(edge,cv2.RETR_TREE,cv2.CHAIN_APPROX_NONE)

       

        #3print("-----------------------------")

        #print((contours[2]))

        bounding_boxes = [cv2.boundingRect(cnt) for cnt in contours[1]]

        index_list = []

        #print(contours[2][0])

        #找到有内轮廓的轮廓

        for i in range (len(bounding_boxes)):

             hie = contours[2][0][i]

             j=0;

             while(hie[2]>0):

                 j+=1

                 hie = contours[2][0][hie[2]]

             if j>2:

               index_list.append(i)

             

        for i,box in enumerate(bounding_boxes):

            if i in index_list:

                if box[2]>1.5*box[3] or box[2]<0.6*box[3] or box[3]*box[2]>10000:

                    index_list.remove(i)

               

                else:

                    cv2.rectangle(img, (box[0], box[1]), (box[0] + box[2], box[1] + box[3]), (0, 255, 0), 2)

       

        nei_list = []    

        for i in index_list:

             hie = contours[2][0][i]

             j=0;

             while(hie[2]>0):  

                 hie = contours[2][0][hie[2]]

                 h1 =  bounding_boxes[i][3]

             nei_list.append(hie[3])

             

        nei_list.sort()

       

        index_list = []

        for l in nei_list:

            if l not in index_list:

                index_list.append(l)

                box = bounding_boxes[l]

                cv2.rectangle(img, (box[0], box[1]), (box[0] + box[2], box[1] + box[3]), (0, 255, 0), 2)

       

        points =[]

        cv2.imshow("img",img)

        cv2.waitKey(10)

        if len(index_list)>=4:

             for l in index_list:

                x,y,w,h = bounding_boxes[l]

                if w*h<400:

                    points.append([int(x+w/2),int(y+h/2)])

                   

        if(len(points)==4):

               points.sort()

               if(points[0][1]<points[1][1]):

                      left_top_p = points[0]

                      left_down_p = points[1]

               else:

                      left_top_p = points[1]

                      left_down_p = points[0]

                     

               

               if(points[2][1]<points[3][1]):

                      right_top_p = points[2]

                      right_down_p = points[3]

               else:

                      right_top_p = points[3]

                      right_down_p = points[2]

           

               return left_top_p,left_down_p,right_top_p,right_down_p,h1

        return 0,0,0,0,0  

    def img_jiaozheng(left_top_p,left_down_p,right_top_p,right_down_p,image_path,h1):

           #if (right_down_p[0]-left_top_p[0])>(right_down_p[1]-left_top_p[1]):

             #  w = right_down_p[0]-left_top_p[0]

           #else:

              # w = right_down_p[1]-left_top_p[1]

        tl = left_top_p

        tr = right_top_p

        br = right_down_p

        bl = left_down_p

       

        widthA = np.sqrt(((br[0] - bl[0])**2)+((br[1] - bl[1])**2))

        widthB = np.sqrt(((tr[0] - tl[0])**2)+((tr[1] - tl[1])**2))

        maxWidth = max(int(widthA),int(widthB))

       

        heightA = np.sqrt(((tr[0] - br[0])**2)+((tr[1] - br[1])**2))

        heightB = np.sqrt(((tl[0] - bl[0])**2)+((tl[1] - bl[1])**2))

        maxheight = max(int(heightA),int(heightB))

       

        target_lf_t = [0,0]

        target_rg_t =[maxWidth-1,0]

        target_rg_d = [maxWidth-1,maxheight-1]

        target_lf_d = [0,maxheight-1]

        #开始校正

        pointSrc = np.float32([tl, tr, br, bl])  # 原始图像中 4点坐标

        pointDst = np.float32([target_lf_t, target_rg_t, target_rg_d, target_lf_d])  # 变换图像中 4点坐标

        MP = cv2.getPerspectiveTransform(pointSrc, pointDst)  # 计算投影变换矩阵 M

        imgP = cv2.warpPerspective(frame, MP, (maxWidth,maxheight))

        im_crop = imgP[int(maxheight/22):maxheight-int(maxheight/22),int(maxWidth/22):maxWidth-int(maxWidth/22),:]    

        cv2.imwrite(image_path,im_crop)

        cv2.imshow("img",im_crop)      

        cv2.waitKey(0)

         # 销毁窗口

        cv2.destroyAllWindows()

    '''

         

    def  get_coodrinate(image_path):

        img=cv2.imread(image_path)

        w = 20

        h = 5

        circle_x=[]

        circle_y=[]

        circle_size=[]

        params = cv2.SimpleBlobDetector_Params()

       

        params.filterByArea = True

        params.minArea = 10e1

        params.maxArea = 10e3

        params.minDistBetweenBlobs = 25

       

        params.filterByConvexity = False

     

        gray= cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)

       

        minThreshValue = 55

        _, gray = cv2.threshold(gray, minThreshValue, 255, cv2.THRESH_BINARY_INV)

       

       

        cv2.imshow("gray",gray)

     

        detector = cv2.SimpleBlobDetector_create(params)

        keypoints = detector.detect(gray)

     

        #result show

        for kp in keypoints:

            x=kp.pt[0]

            y=kp.pt[1]

            size=kp.size

            response=kp.response

            circle_data=circle(x,y,size)

            circle_x.append(circle_data.X_location)

            circle_y.append(circle_data.Y_location)

            circle_size.append(circle_data.radius)

        print('x:',circle_x)

        print('y:',circle_y)

        print('radius:',circle_size)

        print(len(keypoints))

        # opencv

        im_with_keypoints = cv2.drawKeypoints(gray, keypoints, np.array([]), (255, 0, 0), cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS)

        # plt

        # im_with_keypoints = cv2.drawKeypoints(gray, keypoints, np.array([]), (0, 0, 255),  cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS)

        color_img = cv2.cvtColor(im_with_keypoints, cv2.COLOR_BGR2RGB)

       

        cv2.imshow('findCorners', color_img)

        cv2.waitKey(0)

        cv2.destroyAllWindows()

    def main():

       

     circle.get_original(image_path)

     #left_top_p,left_down_p,right_top_p,right_down_p,h1=circle.find_rect(image_path)

     #circle.jiaozheng(left_top_p,left_down_p,right_top_p,right_down_p,image_path,h1)

     circle.get_coodrinate(image_path)

     

circle.main()



最后的最后,我的感想是,好多东西,不像看上去难的那么高不可攀,少说话,多做事,功夫到了,自然水到渠成,来一句鸡血:你的努力终将将你带上最终的高峰,祝国奖!

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值