[opencv] コンピューター ビジョン: 駐車場の駐車スペースのリアルタイム識別

目次

目標

全体的なプロセス

背景

詳しく説明してください


序文

数日前、巨大な人工知能学習 Web サイトを発見しました。わかりやすくてユーモアがあるので、みんなと共有せずにはいられませんでした。 クリックするとウェブサイトにジャンプします

目標

リアルタイムの駐車場監視ビデオで、車の台数と空き駐車スペースの数を確認したいと考えています。そうすれば、空いているスペースにマークを付けることができ、車が来たら、どこに駐車するのが最も便利で最も近い場所であるかをリアルタイムで教えてくれます。 ! !現代のインテリジェントな無人駐車場を実現!

全体的なプロセス

OpenCV に基づく画像処理手法は、駐車場の空き駐車スペースのリアルタイム監視と正確な位置特定の問題を解決するために使用されます。まず、リアルタイム監視録画情報を画像情報に変換する。画像をモルフォロジー処理し、駐車場の要点を特定 マスク画像と原画像を用いて駐車スペース領域の背景を融合 処理後、ハフライン検出法を用いて駐車スペースを検出画像を分割し、各駐車スペースを分割して番号を付けます。最後に、Keras ニューラル ネットワークを使用して駐車スペースと空き駐車スペースをトレーニングし、現在の画像内の駐車スペースが空いているかどうかを判断してリアルタイムで更新し、画像ストリームを出力してリアルタイムのタスクを完了します。空き駐車スペースの監視。

背景

自動車産業の急速な発展と人々の生活水準の向上により、我が国の車の台数は増え続けていますが、駐車スペースの数は限られているため、駐車が困難で駐車効率が低く、時間の無駄が生じています。渋滞や事故などを引き起こすこともあります。駐車スペースの数と空き駐車スペースの位置情報をリアルタイムに検出して把握することで、リソースと時間の無駄を回避し、効率を向上させ、駐車スペースの管理を容易にすることができます。したがって、駐車スペースの視覚化は特に重要です。従来の視覚ベースの駐車スペース検出方法には、検出精度が低く、シーン環境の要件が高いなどの問題がありました。この記事では、エッジ検出によって駐車スペースを分割し、画像の背景をフィルタリングおよび再配置して有用な領域を抽出し、駐車スペースが空いているかどうかを判断してリアルタイムで更新し、画像ストリームとして出力することを目的としています。

詳しく説明してください

まず理解する必要があるのは、ビデオの場合、フレームごとの画像で構成されているため、ビデオを処理することは画像を処理することと同じであるということです。前のフレームの画像と次のフレームの接続フレーム処理された画像はビデオ処理の結果です。
画像を処理するときは、まずビデオ内の写真を使用して、駐車場画像の特定のフレームを確認します。

これは駐車場の 1 フレームの画像です。実際、ここには多くの車がいます。車がなければ、空いている駐車場であることを意味し、検出効果は非常に優れています。まず、画像に対していくつかの形態学的操作を実行する必要があります。グレースケール空間変換、画像の二値化、エッジ検出、輪郭検出、マスキングなどの処理がありますので、以下に一つずつ紹介していきます。
まず、画像表示関数を定義します。

    def cv_show(self,name,img):
        cv2.imshow(name, img)
        cv2.waitKey(0)
        cv2.destroyAllWindows()

ここでは詳しい説明は省きますが、単なる画像を表示する機能です。

    def convert_gray_scale(self,image):
        return cv2.cvtColor(image, cv2.COLOR_RGB2GRAY)

ここで画像はグレースケールに変換されます。

    def select_rgb_white_yellow(self,image): 
        #过滤掉背景
        lower = np.uint8([120, 120, 120])
        upper = np.uint8([255, 255, 255])
        white_mask = cv2.inRange(image, lower, upper)
        self.cv_show('white_mask',white_mask)
        
        masked = cv2.bitwise_and(image, image, mask = white_mask)
        self.cv_show('masked',masked)
        return masked

lower_red と upper_red より高い部分がそれぞれ 0 になり、 lower_red 〜 upper_red の間の値が 255 になります。これは、背景をフィルタリングして無駄なものを除去したことと同じです。つまり、階調が 120 未満または 255 より高いことになります。それらをすべて 0 (黒) に設定し、120 ~ 255 の間のすべてを白に設定します。バイナリイメージの操作と同等です。加工した画像は以下の通りです。

次にエッジ検出を実行しました。これらはすべて OpenCV の形態学的操作です。

    def detect_edges(self,image, low_threshold=50, high_threshold=200):
        return cv2.Canny(image, low_threshold, high_threshold)

エッジ検出の結果は次のとおりです。ここでは、演算のために中間駐車場のローカルエリアを取得する必要があるため、関心のある区間を抽出する演算を実行する必要があります。

    def select_region(self,image):
        rows, cols = image.shape[:2]
        pt_1  = [cols*0.05, rows*0.90]
        pt_2 = [cols*0.05, rows*0.70]
        pt_3 = [cols*0.30, rows*0.55]
        pt_4 = [cols*0.6, rows*0.15]
        pt_5 = [cols*0.90, rows*0.15] 
        pt_6 = [cols*0.90, rows*0.90]

        vertices = np.array([[pt_1, pt_2, pt_3, pt_4, pt_5, pt_6]], dtype=np.int32) 
        point_img = image.copy()       
        point_img = cv2.cvtColor(point_img, cv2.COLOR_GRAY2RGB)
        for point in vertices[0]:
            cv2.circle(point_img, (point[0],point[1]), 10, (0,0,255), 4)
        self.cv_show('point_img',point_img)       
        return self.filter_region(image, vertices)

これらの点は独自のプロジェクトに基づいており、この 6 つの点を使用して駐車場全体をフレーム化し、フレーム化された画像を抽出することが目的です。 ROI エリアとも呼ばれます。結果はこれです。

ここの座標に対して自分で位置決め操作を実行し、マスク画像を作成します。これは、マークされた 6 つの点を ROI 領域に計画し、領域の内側を白いピクセル値に設定し、外側の領域をフルに設定します。黒のピクセル値。次に、イメージとマスクに対して AND 演算と同等の操作を実行します。結果は次のとおりです。

最終的な ROI 領域は次のとおりです。

ここでは、駐車場の大まかな輪郭を取得し、その後、駐車スペースに対する具体的な操作を開始します。まず、駐車場の線形操作を検出する必要があり、このプロジェクトを実行するにはハフ線形検出を使用します。

    def hough_lines(self,image):       
        return cv2.HoughLinesP(image, rho=0.1, theta=np.pi/10, threshold=15, minLineLength=9, maxLineGap=4)

ここでハフ ライン検出は定義されたモデルなので、直接呼び出すことができます。ここでパラメータを紹介しましょう。

image: 処理対象の画像を示します。
rho: 処理の精度を示します。精度が小さいほど直線が正確に検出され、精度の値を大きくすると検出される線分が少なくなります。
theta: 検出された直線角度。直線角度が超えてはならない値を示します。このしきい値を超えると、直線として定義されません。
閾値: ラインの点定義閾値は 15 です。これは実装プロジェクトによって異なります。ラインを形成するピクセル数が 15 を超えた場合にのみ、直線を形成できます。
minLineLength: 最小の長さ。これについてはあまり説明する必要はありません。線の最小長は 9 です。
maxLineGap: 線間の最大ギャップしきい値, from どんなに近くても直線とみなされます。

入力画像は、エッジ検出、minLineLengh (線の最短の長さ、これより短いものは無視される)、および MaxLineCap (2 つの直線間の最大距離。この値より小さい場合は考慮されます) の結果である必要があります。直線)。線分が設定されたしきい値を超えた場合に、ロー距離精度、シータ角度精度、およびしきい値が検出されます。

    def draw_lines(self,image, lines, color=[255, 0, 0], thickness=2, make_copy=True):
        # 过滤霍夫变换检测到直线
        if make_copy:
            image = np.copy(image) 
        cleaned = []
        for line in lines:
            for x1,y1,x2,y2 in line:
                if abs(y2-y1) <=1 and abs(x2-x1) >=25 and abs(x2-x1) <= 55:
                    cleaned.append((x1,y1,x2,y2))
                    cv2.line(image, (x1, y1), (x2, y2), color, thickness)
        print(" No lines detected: ", len(cleaned))
        return image

ここでは引き続き、検出したハフ直線に対してフィルタリング処理を行い、直線の長さが 25 より大きく 55 未満の場合はリストに追加し、左右の座標の差を設定します。直線の端点は 1 を超えないようにします。このようにして、直線をフィルターで除外します。

ここでのテスト結果は図のとおりですが、車両基地内には多くの車両が存在するため、空いている駐車場であればテスト結果は非常に良好になります。検出が完了したら、駐車場の 12 列の各列に対して抽出操作を実行します。たとえば、12 列を取得した後、列ごとに特定の駐車スペースを分離します。次に、1列目と12列目の1台駐車スペースと他の列の2台駐車スペースの処理方法が異なりますので、詳しく見ていきましょう。

    def identify_blocks(self,image, lines, make_copy=True):
        if make_copy:
            new_image = np.copy(image)
        #Step 1: 过滤部分直线
        cleaned = []
        for line in lines:
            for x1,y1,x2,y2 in line:
                if abs(y2-y1) <=1 and abs(x2-x1) >=25 and abs(x2-x1) <= 55:
                    cleaned.append((x1,y1,x2,y2))

まず最初に、いくつかの直線をフィルタリングします。

import operator
        list1 = sorted(cleaned, key=operator.itemgetter(0, 1))

これら 12 列については、各列の左上隅の座標から x1 ~ x12 を取得できるため、これらの列に対してソート操作を実行する必要があります。どの列が最初の列で、どの列が 12 番目の列であるかをコンピュータに識別させます。

        clusters = {}
        dIndex = 0
        clus_dist = 10
    
        for i in range(len(list1) - 1):
            distance = abs(list1[i+1][0] - list1[i][0])
            if distance <= clus_dist:
                if not dIndex in clusters.keys(): clusters[dIndex] = []
                clusters[dIndex].append(list1[i])
                clusters[dIndex].append(list1[i + 1]) 
    
            else:
                dIndex += 1

ここでは、ソートされたすべての直線に対して分類操作を実行し、それらの直線を 1 つの列にグループ化します。そして追加します。各列が分離されるまで。

        rects = {}
        i = 0
        for key in clusters:
            all_list = clusters[key]
            cleaned = list(set(all_list))#一列中的所有直线的坐标信息
            if len(cleaned) > 5:
                cleaned = sorted(cleaned, key=lambda tup: tup[1])#对直线进行排序
                avg_y1 = cleaned[0][1]#这个对于一列来说是固定的
                avg_y2 = cleaned[-1][1]#这个对于一列来说是固定的
                avg_x1 = 0
                avg_x2 = 0
                for tup in cleaned:
                    avg_x1 += tup[0]
                    avg_x2 += tup[2]
                avg_x1 = avg_x1/len(cleaned)
                avg_x2 = avg_x2/len(cleaned)
                rects[i] = (avg_x1, avg_y1, avg_x2, avg_y2)
                i += 1
        
        print("Num Parking Lanes: ", len(rects))

次に、各列を操作して、各列の各駐車スペースの座標情報をすべて抽出します。次に、取得した座標を使用して長方形を描画します。

        buff = 7#微调数值
        for key in rects:
            tup_topLeft = (int(rects[key][0] - buff), int(rects[key][1]))
            tup_botRight = (int(rects[key][2] + buff), int(rects[key][3]))
            cv2.rectangle(new_image, tup_topLeft,tup_botRight,(0,255,0),3)
        return new_image, rects

この期間中に、手動で長方形をさらに微調整しました。

    def draw_parking(self,image, rects, make_copy = True, color=[255, 0, 0], thickness=2, save = True):
        if make_copy:
            new_image = np.copy(image)
        gap = 15.5#车位间的差距是15.5
        spot_dict = {} # 字典:一个车位对应一个位置
        tot_spots = 0
        #微调
        adj_y1 = {0: 20, 1:-10, 2:0, 3:-11, 4:28, 5:5, 6:-15, 7:-15, 8:-10, 9:-30, 10:9, 11:-32}
        adj_y2 = {0: 30, 1: 50, 2:15, 3:10, 4:-15, 5:15, 6:15, 7:-20, 8:15, 9:15, 10:0, 11:30}
        
        adj_x1 = {0: -8, 1:-15, 2:-15, 3:-15, 4:-15, 5:-15, 6:-15, 7:-15, 8:-10, 9:-10, 10:-10, 11:0}
        adj_x2 = {0: 0, 1: 15, 2:15, 3:15, 4:15, 5:15, 6:15, 7:15, 8:10, 9:10, 10:10, 11:0}
        for key in rects:
            tup = rects[key]
            x1 = int(tup[0]+ adj_x1[key])
            x2 = int(tup[2]+ adj_x2[key])
            y1 = int(tup[1] + adj_y1[key])
            y2 = int(tup[3] + adj_y2[key])
            cv2.rectangle(new_image, (x1, y1),(x2,y2),(0,255,0),2)
            num_splits = int(abs(y2-y1)//gap)
            for i in range(0, num_splits+1):
                y = int(y1 + i*gap)
                cv2.line(new_image, (x1, y), (x2, y), color, thickness)
            if key > 0 and key < len(rects) -1 :        
                #竖直线
                x = int((x1 + x2)/2)
                cv2.line(new_image, (x, y1), (x, y2), color, thickness)
            # 计算数量
            if key == 0 or key == (len(rects) -1):
                tot_spots += num_splits +1
            else:
                tot_spots += 2*(num_splits +1)
                
            # 字典对应好
            if key == 0 or key == (len(rects) -1):
                for i in range(0, num_splits+1):
                    cur_len = len(spot_dict)
                    y = int(y1 + i*gap)
                    spot_dict[(x1, y, x2, y+gap)] = cur_len +1        
            else:
                for i in range(0, num_splits+1):
                    cur_len = len(spot_dict)
                    y = int(y1 + i*gap)
                    x = int((x1 + x2)/2)
                    spot_dict[(x1, y, x, y+gap)] = cur_len +1
                    spot_dict[(x, y, x2, y+gap)] = cur_len +2   
        
        print("total parking spaces: ", tot_spots, cur_len)
        if save:
            filename = 'with_parking.jpg'
            cv2.imwrite(filename, new_image)
        return new_image, spot_dict

処理の結果は次のようになります。

ここでは、すべての駐車スペースを分割しました。
次に、keras ニューラル ネットワークを使用して、駐車スペースに車があるかどうかを学習したいと思います。ニューラル ネットワークに駐車スペースに車があるかどうかを予測させます。 keras ニューラル ネットワーク全体の学習プロセスは次のとおりです。 VGG16 ネットワークを使用して、駐車スペースに車があるかどうかという 2 つの分類タスクを実行します。駐車スペースの訓練画像をご覧いただけます。このコードを通じて、駐車スペースに車があるかどうかを抽出します。

    def save_images_for_cnn(self,image, spot_dict, folder_name ='cnn_data'):
        for spot in spot_dict.keys():
            (x1, y1, x2, y2) = spot
            (x1, y1, x2, y2) = (int(x1), int(y1), int(x2), int(y2))
            #裁剪
            spot_img = image[y1:y2, x1:x2]
            spot_img = cv2.resize(spot_img, (0,0), fx=2.0, fy=2.0) 
            spot_id = spot_dict[spot]
            
            filename = 'spot' + str(spot_id) +'.jpg'
            print(spot_img.shape, filename, (x1,x2,y1,y2))
            
            cv2.imwrite(os.path.join(folder_name, filename), spot_img)

ここの駐車スペースには車はありませんので、車をお持ちの方は以下の通りです。

files_train = 0
files_validation = 0

cwd = os.getcwd()
folder = 'train_data/train'
for sub_folder in os.listdir(folder):
    path, dirs, files = next(os.walk(os.path.join(folder,sub_folder)))
    files_train += len(files)


folder = 'train_data/test'
for sub_folder in os.listdir(folder):
    path, dirs, files = next(os.walk(os.path.join(folder,sub_folder)))
    files_validation += len(files)

print(files_train,files_validation)

img_width, img_height = 48, 48
train_data_dir = "train_data/train"
validation_data_dir = "train_data/test"
nb_train_samples = files_train
nb_validation_samples = files_validation
batch_size = 32
epochs = 15
num_classes = 2

model = applications.VGG16(weights='imagenet', include_top=False, input_shape = (img_width, img_height, 3))


for layer in model.layers[:10]:
    layer.trainable = False


x = model.output
x = Flatten()(x)
predictions = Dense(num_classes, activation="softmax")(x)


model_final = Model(input = model.input, output = predictions)


model_final.compile(loss = "categorical_crossentropy", 
                    optimizer = optimizers.SGD(lr=0.0001, momentum=0.9), 
                    metrics=["accuracy"]) 


train_datagen = ImageDataGenerator(
rescale = 1./255,
horizontal_flip = True,
fill_mode = "nearest",
zoom_range = 0.1,
width_shift_range = 0.1,
height_shift_range=0.1,
rotation_range=5)

test_datagen = ImageDataGenerator(
rescale = 1./255,
horizontal_flip = True,
fill_mode = "nearest",
zoom_range = 0.1,
width_shift_range = 0.1,
height_shift_range=0.1,
rotation_range=5)

train_generator = train_datagen.flow_from_directory(
train_data_dir,
target_size = (img_height, img_width),
batch_size = batch_size,
class_mode = "categorical")

validation_generator = test_datagen.flow_from_directory(
validation_data_dir,
target_size = (img_height, img_width),
class_mode = "categorical")

checkpoint = ModelCheckpoint("car1.h5", monitor='val_acc', verbose=1, save_best_only=True, save_weights_only=False, mode='auto', period=1)
early = EarlyStopping(monitor='val_acc', min_delta=0, patience=10, verbose=1, mode='auto')




history_object = model_final.fit_generator(
train_generator,
samples_per_epoch = nb_train_samples,
epochs = epochs,
validation_data = validation_generator,
nb_val_samples = nb_validation_samples,
callbacks = [checkpoint, early])

ここでは、畳み込みニューラル ネットワークを使用して駐車スペースの有無を学習し、ニューラル ネットワークの学習を通じて画像フレームの判断を開始します。結果は次のとおりです。

    def make_prediction(self,image,model,class_dictionary):#预测
        #预处理
        img = image/255.
    
        #转换成4D tensor
        image = np.expand_dims(img, axis=0)
    
        # 用训练好的模型进行训练
        class_predicted = model.predict(image)
        inID = np.argmax(class_predicted[0])
        label = class_dictionary[inID]
        return label
    def predict_on_image(self,image, spot_dict , model,class_dictionary,make_copy=True, color = [0, 255, 0], alpha=0.5):
        if make_copy:
            new_image = np.copy(image)
            overlay = np.copy(image)
        self.cv_show('new_image',new_image)
        cnt_empty = 0
        all_spots = 0
        for spot in spot_dict.keys():
            all_spots += 1
            (x1, y1, x2, y2) = spot
            (x1, y1, x2, y2) = (int(x1), int(y1), int(x2), int(y2))
            spot_img = image[y1:y2, x1:x2]
            spot_img = cv2.resize(spot_img, (48, 48)) 
            
            label = self.make_prediction(spot_img,model,class_dictionary)
            if label == 'empty':
                cv2.rectangle(overlay, (int(x1),int(y1)), (int(x2),int(y2)), color, -1)
                cnt_empty += 1
                
        cv2.addWeighted(overlay, alpha, new_image, 1 - alpha, 0, new_image)
                
        cv2.putText(new_image, "Available: %d spots" %cnt_empty, (30, 95),
        cv2.FONT_HERSHEY_SIMPLEX,
        0.7, (255, 255, 255), 2)
        
        cv2.putText(new_image, "Total: %d spots" %all_spots, (30, 125),
        cv2.FONT_HERSHEY_SIMPLEX,
        0.7, (255, 255, 255), 2)
        save = False
        
        if save:
            filename = 'with_marking.jpg'
            cv2.imwrite(filename, new_image)
        self.cv_show('new_image',new_image)
        
        return new_image

これが画像のトレーニング結果です。見てみましょう。

予測の結果、合計 555 台の駐車スペースが検出され、現在 113 台の無料駐車スペースがあります。次に、ビデオに対して同じ操作を実行します。主にビデオをフレームごとに画像に分割し、画像の各フレームに対して次の画像操作を実行します。このようにして、ビデオストリームの形式で出力できます。これがプロジェクト全体の流れです。

ここでは、keras 畳み込みニューラル ネットワークを使用して画像を継続的にトレーニングおよびテストし、リアルタイムの駐車スペース情報を取得します。これでプロジェクトは終了です。車が行き交う駐車場における駐車効率の問題を対象として、OpenCVに基づいた駐車スペースの空き状態検出手法を提案し、ビデオの画像の各フレームを単位として、グレースケールやハフライン検出などの手法を使用します。 Keras ニューラル ネットワーク モデルを処理し、最終的に使用して、処理されたデータをトレーニングおよび予測し、駐車スペースが空いているかどうかを判断します。テスト結果は、この方法が駐車場の駐車スペースの状況をリアルタイムで監視するタスクを迅速に完了できることを示しています。しかし、駐車場での駐車効率を向上させるためには、駐車場内の駐車マークのメンテナンスが時期尚早に行われるため、駐車スペースのマークが不鮮明になったり、検出精度に影響を与える深刻なオクルージョンなどの問題が依然として発生します。画像前処理の計算量は削減されていますが、計算量が多く、プログラム処理に時間がかかるため、論文の欠点を補うためにさらなる研究と改良を行う予定です。将来の研究作業では、より高速なアルゴリズムを使用して、画像前処理プロセスにおける計算量の多い問題に関するこの方法の時間のかかる問題をさらに改善することを試みることができます。

ブロガーさんの記事が良い、使えそうだと思ったら無料でフォローできますので、3回連続で集めて応援していただけるとさらにお得です!これが私にできる最大のサポートです!

おすすめ

転載: blog.csdn.net/m0_68662723/article/details/134534006