無人車両の色認識における OpenCV の HSV 色空間の応用

RGBは誰にとっても最も馴染みのある三原色空間に属しており、目に見えるあらゆる色は三原色を混ぜることで作ることができます。しかし、色空間における画像の効果的な処理は一般的に HSV 空間で実行されます。HSV (色相、彩度、明度値) は、色の直感的な特性に従って作成された色空間であり、六角錐モデルとしても知られています。

 

OpenCV の HSV 色空間の値の範囲 => H:[0, 180], S:[0, 255], V:[0, 255]、H 色相が小さいほど赤に近づき、値が大きいほど青に近づきます。この式は、単純赤を表すのに赤を使用するよりも正確です。S 彩度が小さいほど色は明るく、色が大きいほど色濃くなります。V 輝度が小さいほど、暗くなるほど、大きくなるほど明るくなります上の写真の色の変化に注目してください。HSV を選択する理由は、H で表される色相は基本的に特定の色を決定でき、彩度や明度の情報と組み合わせることで、特定のしきい値より大きいと判断できるためです。RGBは3つの成分で構成されていますが、各成分の寄与率を判断する必要があります。HSV空間の認識範囲が広くなり、より使いやすくなりました。

1. デモンストレーション例

 例を見てみましょう。外側が青いパッケージの幅広のタバコと幅狭のタバコのパックを取り出し、色を識別して追跡します。

1.1. 色の認識と追跡

私のデスクトップにはカメラが搭載されていないので、ここでは無人車両のカメラを使用していますが、カメラ映像の取得方法はOpenCVとは少し違いますが、似ていますが、やはりOpenCVベースとなっています。

from jetbotmini import Camera
from jetbotmini import bgr8_to_jpeg
import cv2
import numpy as np
import traitlets
import ipywidgets.widgets as widgets
from IPython.display import display

# 目标颜色,这里设置为蓝色数组
color_lower = np.array([100,43,46])
color_upper = np.array([124, 255, 255])
# 相机实例
camera = Camera.instance(width=720, height=720)
# 显示控件(视频也是图片的连续帧)
color_image = widgets.Image(format='jpeg', width=500, height=400)
display(color_image)

# 实时识别颜色并反馈到上面的控件里
while 1:
    frame = camera.value # (720, 720, 3) (H,W,C)
    frame = cv2.resize(frame, (400, 400)) # (400, 400, 3)
    frame = cv2.GaussianBlur(frame,(5,5),0) # 高斯滤波(5, 5)表示高斯矩阵的长与宽都是5,标准差为0
    hsv = cv2.cvtColor(frame,cv2.COLOR_BGR2HSV)#将BGR转成HSV
    mask=cv2.inRange(hsv,color_lower,color_upper)
    mask=cv2.erode(mask,None,iterations=2) # 进行腐蚀操作,去除边缘毛躁
    mask=cv2.dilate(mask,None,iterations=2) # 进行膨胀操作
    mask=cv2.GaussianBlur(mask,(3,3),0)
    cnts=cv2.findContours(mask.copy(),cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)[-2] # 轮廓
    if len(cnts)>0:
        cnt = max(cnts,key=cv2.contourArea) # 轮廓面积
        (color_x,color_y),color_radius=cv2.minEnclosingCircle(cnt) # 外接圆的位置信息
        if color_radius > 10:
            # 圆圈标注
            cv2.circle(frame,(int(color_x),int(color_y)),int(color_radius),(255,0,255),2)
    color_image.value = bgr8_to_jpeg(frame) # 转成图片传入Image组件

追跡画面ではピンクの丸が青になっているのがわかります。その中には、カメラとウィジェットの使用に関する Web インタラクティブなコンポーネントが多数ありますので、興味がある場合は、無人車両のカメラのリアルタイムキャプチャ画像とウィジェットの関連操作を参照してください。

このコードの意味は比較的明確で、カメラをインスタンス化しさまざまな色の HSV 色域空間に従って画像の各フレームを分類してマークし、まずcv2 . labelを通じてBGR (ここで読み取られる画像は RGB ではなく BGR です) を HSV に変換します環境光が十分であるときに認識効果が理想的でない場合は、無限ループで一部のパラメーター設定を手動で変更できます。

1.2、cv2.inRange

次に、上記のコード内のいくつかの関数について説明しますマスク
を作成するとき、 mask=cv2.inRange(hsv,color_ lower,color_upper) は、下限配列より下で上限配列より大きい値が 0 黒で、その間の値が 255 白であり、単一のチャネルに属することを意味します。直感的に理解するためにコードを見てみましょう。 

import cv2
import matplotlib.pyplot as plt

img_cv2 = cv2.imread('test.jpg')
hsv = cv2.cvtColor(img_cv2, cv2.COLOR_BGR2HSV)
lowerb = np.array([20, 20, 20])
upperb = np.array([200, 200, 200])

# 黑白单通道(H,W)
mask = cv2.inRange(hsv, lowerb, upperb)
cv2.imshow('Display', mask)
cv2.waitKey(0)
cv2.destroyAllWindows()

plt.subplot(1,2,1); plt.imshow(img_cv2,aspect='auto');plt.axis('off');plt.title('BGR')
plt.subplot(1,2,2); plt.imshow(mask,aspect='auto');plt.axis('off');plt.title('mask')
plt.show()

 以下に示す BGR とマスク:

 

1.3、cv2.erode および cv2.dilate

腐食操作はイメージの形態に属します。文字通りの意味と同じです。腐食が実行されます。この関数のヘルプを確認できます: erode( src, kernel[, dst[,アンカー[, iterations[, borderType[, borderValue]]]]]) -> dst 元の src イメージのサイズはターゲット イメージ dst のサイズと同じです。ここでのカーネル コアのサイズは、オプションの腐食の
サイズ
決定ますコードテストでは次のようになります。

import cv2
import numpy as np

image = cv2.imread('test.jpg')
kernel = np.ones((5, 5), np.uint8)
image = cv2.erode(image, None)
cv2.imshow('erode', image)
cv2.waitKey(0)
cv2.destroyAllWindows()

ここではimage = cv2.erode(image, None) 2 番目のカーネル パラメーターは指定するかどうかを指定できます。image = cv2.erode(image, kernel)を指定した後、カーネルのサイズを変更して効果を確認します。

本質は畳み込み演算を行うことであり、演算中の数値が1でない場合は0とし、全て1の場合のみ1とします。この関数はコード内のコメントのようなもので、エッジなどのノイズ グリッチを除去するために使用されます。cv2.dilate拡張関数は腐食関数の逆の操作とみなすことができ、腐食後は黒い部分が膨張し、拡張関数は縮小します。

1.4、cv2.GaussianBlur

ガウスぼかしはノイズ除去にも使用されます。画像にガウス ノイズを追加する方法を見てから、この関数を使用して、主にガウス ノイズに対してノイズ除去効果を作成します。 

import cv2  as cv
import numpy as np
 
def myShow(name,img):
    cv.imshow(name,img)
    cv.waitKey(0)
    cv.destroyAllWindows()
# 加高斯噪声
def addGauss(img,mean=0,val=0.01):
    img = img / 255
    gauss = np.random.normal(mean,val**0.05,img.shape)
    img = img + gauss
    return img

img = cv.imread('gauss.png')
img1 = addGauss(img)
myShow('img1',img1)

img2 = cv.GaussianBlur(img1,(3,3),0)
myShow('img2',img2)

ここでは、以下に示すように、元の画像と、ガウス ノイズとノイズ リダクション処理を施した 3 つの画像を組み合わせます。

1.5、cv2.findContours およびdrawContours 

輪郭関数findContours(image, mode, method[, contours[,hierarchy[, offset]]]) -> contours、階層はバイナリイメージ で検出されるため、最初にグレースケール イメージに変換し、しきい値を介してバイナリ イメージに変換します。
最後に、アウトラインを描画し、drawContours(image, contours, contourIdx, color[, height[, lineType[,hierarchy[, maxLevel[, offset]]]]] -> image を使用します。コードの実装を見てみましょう

import cv2
import numpy as np

image = cv2.imread('test.jpg')
gray = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY)
ret, binary = cv2.threshold(gray,0,255,cv2.THRESH_BINARY+cv2.THRESH_OTSU)
contours, hierarchy = cv2.findContours(binary, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
image = cv2.drawContours(image,contours,-1,(255,0,0),2)

#cv2.imshow('erode',binary)
cv2.imshow('erode',image)
cv2.waitKey(0)
cv2.destroyAllWindows()

 

cv2.RETR_EXTERNAL : 輪郭の内側の構造を無視して、外側の輪郭を検出します。
cv2.CHAIN_APPROX_SIMPLE : 要素を水平、垂直、対角方向に圧縮し、この方向の終点座標のみを保持します。たとえば、行列のアウトラインでは、アウトライン情報を保存するために 4 つの点だけが必要です。
cv2.CHAIN_APPROX_NONE : すべての輪郭点を保存し、隣接する 2 つの点間のピクセル位置の差が 1 を超えないようにします。

1.6、HSV カラー値 

ここでは例として青を使用していますが、他の色が必要な場合はどうすればよいでしょうか? 以下の図に示すように、HSV の値は何ですか。

 

2. OpenCV のナレッジポイント

ここでは OpenCV の知識が多く使用されますが、画像の読み取りやグレー画像への変換など、よく使用されるものに慣れてみましょう。 

2.1、画像の読み取りと表示 

import cv2

img = cv2.imread('test.jpg', 0)
cv2.imshow("image",img)
# 如果注释下面的等待按键和释放窗口资源,会出现“窗口未响应”的状态,不能正常显示图片
cv2.waitKey()
cv2.destroyAllWindows()

 もちろん、このビジュアル ライブラリがインストールされていない場合は、次のエラーが報告されます: ModuleNotFoundError: No module names 'cv2'

インストールコマンド: pip install opencv-python -i http://pypi.douban.com/simple/ --trusted-host pypi.douban.com 

イメージcv2.imread()を読み取るとき、2 番目のパラメータは 0 で、グレースケール モードを示します。0の代わりにcv2.IMREAD_GRAYSCALE
を使用することもできます。他の数値は次のとおりです:
1 はカラー イメージを示しますcv2.IMREAD_GRAYSCALE
2 はチャネルを含むモードを変更しないことを示しますcv2.IMREAD_UNCHANGED 

2.2、画像の読み取りと保存

img = cv2.imread('test.jpg', 0)
cv2.imwrite('new.jpg', img)

このようにして、グレースケールモードで読み込んだ画像は、cv2.imwriteによって新規画像として保存されます。

2.3、ビデオの読み取りと表示 

同様に、ビデオの効果を見てみましょう。一般的な理解は、ビデオはその中の各フレーム (画像) を継続的に読み込むということです。 

import cv2

cap = cv2.VideoCapture('test.mp4')
print(cap.read()[1].shape) # (1080, 1920, 3)

 このようにして動画の 1 フレームを読み込み、戻り値はタプルになりますが、動画全体を読み込んだ場合はどうなるでしょうか。見てみましょう:

import cv2

cap = cv2.VideoCapture('test.mp4')
if (cap.isOpened() == False):
    print('不能打开视频文件')
else:
    fps = cap.get(cv2.CAP_PROP_FPS) # 参数可直接使用5代替
    print('每帧速度:', fps,'FPS') # 每帧速度: 29.97002997002997 FPS
    f_count = cap.get(cv2.CAP_PROP_FRAME_COUNT) # 7
    print('总帧数: ', f_count) #总帧数:  3394.0

while(cap.isOpened()):
    ret, frame = cap.read()
    if ret == True:
        cv2.imshow('Frame',frame)
        key = cv2.waitKey(20)
        # 按q键退出
        if key == ord('q'):break
    else:
        break

# 注意释放资源
cap.release()
cv2.destroyAllWindows()

このようにして、画像が表示されるのと同じようにビデオが表示されます。このうち、cv2.waitKey(20)はフレーム間で20ミリ秒待つことを意味しており、値が大きいほど待ち時間が長くなり、動画の再生が遅くなることがわかります。
もちろん、より重要なことは、カメラ監視のビデオを取得することです。

# 参数 0 表示设备的默认摄像头,当设备有多个摄像头时可以改变参数选择
cap = cv2.VideoCapture(0)

もちろん、一部の外部カメラの ID は 0 ではない可能性があります。トラバーサルを使用してそれを取得できます。

import cv2
ID = 0
while(1):
    cap = cv2.VideoCapture(ID)
    ret, frame = cap.read()
    if ret == False:
        ID += 1
    else:
        print(ID)
        break

 2.4. 複数の写真を組み合わせてビデオを作成する

ディレクトリ内の画像をビデオに書き込む方法は前の方法と似ていますが、合成ビデオ内の画像のサイズは同じである必要があることに注意してください。つまり、ディレクトリ内の画像のサイズが異なる場合、単にビデオを直接書き込むだけでは失敗します。そのため、ここでは同じになるように画像をトリミングし、補間方法を使用して操作する必要があります

import cv2
import os
path ='imgs'
size = (600,400) # (W,H)
fps = 1
#fourcc = cv2.VideoWriter_fourcc('X','V','I','D')
fourcc = cv2.VideoWriter_fourcc(*'XVID')
video = cv2.VideoWriter('hi.avi',fourcc,fps,size)

for item in os.listdir(path):
    if item.lower().endswith('.jpg'):
        img = cv2.imread(os.path.join(path,item))
        img1 = cv2.resize(img, size, interpolation=cv2.INTER_CUBIC)
        print(img1.shape) # (H,W,C)
        video.write(img1)
video.release()
cv2.destroyAllWindows()

interpolationパラメータで指定される補間方法:

INTER_NEAREST : 最近傍補間
INTER_LINEAR : バイリニア補間、デフォルト
INTER_CUBIC : 4x4 ピクセル近傍内のバイキュービック補間
INTER_LANCZOS4 : 8x8 ピクセル近傍内のランチョス補間

2.5、直線、長方形、円、その他の形状

実際のアプリケーションで非常に一般的ないくつかの一般的な形状を以下に示します。

2.5.1. 直線

cv2.line(img, startPoint, endPoint, color, thickness)
startPoint	:起始位置像素坐标
endPoint:结束位置像素坐标
color:绘制的颜色
thickness:绘制的线条宽度

2.5.2、丸

cv2.circle(img, centerPoint, radius, color, thickness)
img:需要绘制的目标图像对象
centerPoint:绘制的圆的圆心位置像素坐标
radius:绘制的圆半径
color:绘制的颜色
thickness:绘制的线条宽度(thickness 是负数,表示圆被填充)

2.5.3. 長方形

cv2.rectangle(img, point1, point2, color, thickness)
img:需要绘制的目标图像对象
point1:左上顶点位置像素坐标
point2:右下顶点位置像素坐标
color:绘制的颜色
thickness:绘制的线条宽度

2.5.4. テキスト

cv2.putText(img, text, point, font, size, color, thickness)
img:需要绘制的目标图像对象
text:绘制的文字
point:左上顶点位置像素坐标
font:绘制的文字格式
size:绘制的文字大小
color:绘制使用的颜色
thickness:绘制的线条宽度

2.5.5. 画像のスケーリング

cv2.resize(InputArray src, OutputArray dst, Size, fx, fy, interpolation)
InputArray src:输入图片
OutputArray dst:输出图片
Size:输出图片尺寸
fx, fy:沿x轴,y轴的缩放系数
interpolation:插值方法

3、bgr8_to_jpeg

最後に、画像コンポーネントに画像を表示する必要がある場合、各フレームの画像を画像コンポーネントにフィードバックする必要があります。その場合、各フレームの画像は BGR であり、画像コンポーネントの形式は jpeg であるため、変換が必要になります。
関数bgr8_to_jpegは、画像をメモリ バッファーにエンコードするもので、これは本質的に、画像を圧縮するimencode
関数のカプセル化です。関数のソース コードは次のとおりです。

def bgr8_to_jpeg(value, quality=75):
    return bytes(cv2.imencode('.jpg', value)[1])

内部のimencode関数は次のとおりです。

imencode(ext, img[, params]) -> retval, buf
ext:定义输出格式的文件扩展名
img:要写入的图像
buf:输出缓冲区调整大小,以适应压缩图像

関数のソース コードや、ソース ファイルを表示するのが不便な状況をすばやく表示します。

import inspect
print(inspect.getsource(bgr8_to_jpeg))

おすすめ

転載: blog.csdn.net/weixin_41896770/article/details/131746841