pythonメニュー

Pythonで扱いイメージ( OpenCV )

画像処理用のモジュールで、pip install opencv-python でモジュール追加が必要です。

OpenCVでWebカメラの撮影

import cv2 # OpenVC のカメラ利用

camera = cv2.VideoCapture(0) # カメラをオープンする(カメラ準備処理)
# カメラデバイスの IDを引数で指定するが、カメラが1台しか接続されていない場合は、0を指定

success, frame_img = camera.read() # カメラ画像取得する。
print(success, frame_img, frame_img.shape); 

cv2.imshow("title", frame_img) #  ウインドウ表示

code = cv2.waitKey(0) # 任意のキー入力を待つ
print( "%d '%c'"% (code , chr(code )) )

cv2.destroyAllWindows() #  ウインドウ表示を終了

# ファイルの保存
cv2.imwrite("opencv1.png",frame_img) 
cv2.imwrite("opencv1.jpg",frame_img) 

上記は、カメラデバイスを開いて、撮影して、表示して、保存するコード

OpenCVでWebカメラの撮影

以下では、カメラ解像度を、小さい適当サイズに設定し、 その後でカメラ撮影と表示をループさせ、 希望の撮影画面で終了して、最後の撮影画面を .pngで保存させています。

import cv2 # OpenVC のカメラ利用

camera = cv2.VideoCapture(0) # カメラをオープンする(カメラ準備処理)

camera.set(cv2.CAP_PROP_FRAME_WIDTH, 100) # 横幅設定で縦も自動設定される
print(f"FRAME_WIDTH :{ camera.get(cv2.CAP_PROP_FRAME_WIDTH) }")
print(f"FRAME_HEIGHT:{ camera.get(cv2.CAP_PROP_FRAME_HEIGHT) }")

while True:
    success, frame_img = camera.read() # カメラ画像取得する。
    if not  success : break
    cv2.imshow("title", frame_img) # ウインドウ表示
    print(frame_img,  frame_img.shape )
    code = cv2.waitKey(500) # 0.5秒待つ
    if code == ord('q'): break

cv2.destroyAllWindows() # ウインドウを閉じる

cv2.imwrite("opencv1.png",frame_img) # ファイルの保存

print(cv2.CAP_PROP_FRAME_WIDTH)で表示される番号が CAP_PROP_FRAME_WIDTHプロパティに記憶される値です。
CAP_PROP_FRAME_WIDTH以外にどんなプロパティがあるかは、 次の表現で、リストに文字列で取得できます。

p_list=[x for x in dir(cv2) if x.startswith('CAP_PROP')]
# 上記で得られたのプロパティ文字列から動的にその値を取得するコードを以下に示す。
for i in range(len(p_list)):
   p_name=p_list[i] # プロパティ名の文字列取得
   p_idx=getattr(cv2, p_name) # # プロパティ名文字列から値を取得
   print(p_name, camera.get(p_idx))

OpenCVで画像ファイルを取り込んで、鏡に移したように左右を入れ替える。

左が前述のコードで OpenCVを使って撮影で得られた画像で、中央が自作の左右反転関数で得た画像、右が左右反転プログラム失敗の画像です。
  

撮影画像、     左右反転プログラムで加工画像、  プログラムミスでできた画像
左の「opencv1.png」を、自作のmirror関数で左右反転させている。上記右画像は、失敗作のmirror_F関数を使った画像です。

import cv2 # OpenVC 利用

def mirror(frame_img): # ミラー反転の正しい関数定義例
    for line in frame_img:
        n = len(line) # 水平の画素数
        print(n, frame_img.shape)
        line2 = line.copy()
        for i in range(n):
            line[i] = line2[-i]

def mirror_F(frame_img): # ミラー反転しようとして失敗した定義例
    for line in frame_img:
        n = len(line) # 水平の画素数      
        for i in range(n//2):
            tmp= line[i] # 交換のつもりで書いたが参照変数なので実態の交換できないという失敗
            line[i] = line[-i]
            line[-i] = tmp

frame_img = cv2.imread("opencv1.png") # 以前に撮影した画像の読み込み
print( frame_img.shape )
# frame_img = cv2.flip(frame_img, 1) # cv2の反転メソッドを使う場合
# mirror_F(frame_img) # 反転失敗の関数呼び出し
mirror(frame_img) # 正しく反転した関数呼び出し
 
cv2.imshow("processed image", frame_img) # ウインドウに画面表示
code = cv2.waitKey(0) # 押されるの待つ
cv2.destroyAllWindows() # ウインドウを閉じる
cv2.imwrite("opencv2.png",frame_img) # ファイルの保存

OpenCVの描画機能を確認

OpenCVには線分や四角(長方形、正方形)、円、楕円、円弧、矢印、マーカー、文字など、 様々な図形を描画する関数が用意されている。物体を検出した位置を図示したりする際などに便利。 右のような描画例のコード例を示します。

import cv2 # (opencvdraw.py)
import numpy as np

camera = cv2.VideoCapture(0) # カメラをオープンする(カメラ準備処理)
success, frame_img = camera.read() # カメラ画像取得する。
frame_img = np.zeros_like(frame_img)

if not success:frame_img = np.zeros( (300,400,3) , dtype=np.uint8) # カメラが無い場合は、この黒画像を使う 
print(frame_img.shape)
h, w , bgr = frame_img.shape

# 各種描画テスト
cv2.rectangle(frame_img, (5, 5), (w-10, h-10), (0, 255, 255), thickness=-1)#矩形塗り
cv2.rectangle(frame_img, (5, 5), (w-10, h-10), (255, 0, 0), thickness=10)#枠
cv2.arrowedLine(frame_img, (0, 0), (200, 300), (0, 0, 255), thickness=4)#矢印
cv2.circle(frame_img, (100, 100), 50, (0, 255, 0), thickness=-1)#円塗り
cv2.ellipse(frame_img, ((300, 150), (50, 200), 45), (255, 255, 255), thickness=-1)# 楕円
cv2.ellipse(frame_img, (300, 150), (25, 100), 45,0,180, (0, 0, 0), thickness=5)# 楕円弧

cv2.drawMarker(frame_img, (30, 250), (0,0,0),   markerSize=20) # +マークの描画 
cv2.drawMarker(frame_img, (60, 250), (0,0,0), markerType=cv2.MARKER_TILTED_CROSS)
cv2.drawMarker(frame_img, (90, 250), (0,0,0), markerType=cv2.MARKER_STAR)
cv2.drawMarker(frame_img, (120, 250),(0,0,0), markerType=cv2.MARKER_SQUARE)
cv2.drawMarker(frame_img, (150, 250),(0,0,0), markerType=cv2.MARKER_TRIANGLE_UP)
cv2.drawMarker(frame_img, (180, 250),(0,0,0), markerType=cv2.MARKER_DIAMOND)
pts=np.array( [[200,100],[210,50],[220,150],[230,50],[240,150]])
cv2.polylines(frame_img,[pts], False, (255, 255, 0), thickness=2)
cv2.putText(frame_img,'Draw Text',(240,100),cv2.FONT_HERSHEY_SIMPLEX,
    fontScale=1.0,color=(0,0,255),thickness=2)
cv2.line(frame_img, (200, 30), (400, 30), (0,0,0),thickness=2)

cv2.imshow("draw test", frame_img) # ウインドウ表示
print(frame_img,  frame_img.shape )
code = cv2.waitKey(0) 

cv2.destroyAllWindows() # ウインドウを閉じる
cv2.imwrite("opencvdraw.jpg",frame_img) # ファイルの保存

OpenCVの表示ウインドウのマウスイベント処理

イベント登録関数function_nameは、次のように登録します。
cv2.setMouseCallback("name", function_name [, param ])
nameのウインドウ名は、cv2.imshowの第一引数のタイトル名と一致させなければならない。
paramは、必要に応じて指定し、function_nameの第4引数に使われます。

function_nameの仕様は、次の通りです。
def function_name (event, x, y, flags, param): x,yはイベント発生座標、paramは引数を参照渡しすることで、内部を自由に変更できる。
eventの記憶内容を、以下定義と比較してイベントの種類が分かる。以下にその主要な定義を示す

event意味
cv2.EVENT_MOUSEMOVE マウスを移動したとき,またはボタンを離した直後
cv2.EVENT_LBUTTONDOWN 左ボタンを押下したとき
cv2.EVENT_RBUTTONDOWN 右ボタンを押下したとき
cv2.EVENT_MBUTTONDOWN 中ボタンを押下したとき
cv2.EVENT_LBUTTONUP 左ボタンを離したとき
cv2.EVENT_RBUTTONUP 右ボタンを離したとき
cv2.EVENT_MBUTTONUP 中ボタンを離したとき
cv2.EVENT_LBUTTONDBLCLK 左ボタンがダブルクリックされたとき
cv2.EVENT_RBUTTONDBLCLK 右ボタンがダブルクリックされたとき
cv2.EVENT_MBUTTONDBLCLK 中ボタンがダブルクリックされたとき

flagsは下記のビットORがセットされ,どのキーが同時に押されているか判断できる。
flags値(バージョンで変わるかも)と意味
cv2.EVENT_FLAG_LBUTTON 1:左ボタンを押下したとき
cv2.EVENT_FLAG_RBUTTON 2:右ボタンを押下したとき
cv2.EVENT_FLAG_MBUTTON 3:中ボタンを押下したとき
cv2.EVENT_FLAG_CTRLKEY 4:CTRLボタンを押した
cv2.EVENT_FLAG_SHIFTKEY 8:SHIFTボタンを押した
cv2.EVENT_FLAG_ALTKEY 32:ALTボタンを押した
上記flagsは、Python ビット演算 (下記ビット演算を利用して判定)利用する判定ができる。
Javaと同じで、|がビットOR、& ビットANDである。XORは^
例 bin(0b1100 | 0b1010) →'0b1110'
例 bin(0b1100 & 0b1010) →'0b1000'
例 bin(0b1100 ^ 0b0101) →'0b1001'
左クリックで矩形左上端、右クリックで矩形右下端を指定して、その矩形領域を保存する例を下記にし示します。
import cv2 # クリック操作でトリミングして保存(opencvevent.py)
import numpy as np

camera = cv2.VideoCapture(0) # カメラをオープンする(カメラ準備処理)
success, frame_img = camera.read() # カメラ画像取得する。
#frame_img = np.zeros_like(frame_img)
if not success:frame_img = frame_img = cv2.imread("opencv1.png") # カメラがない時に使う画像
print(frame_img.shape)
h, w , d = frame_img.shape

print([s for s in dir(cv2) if s.startswith('EVENT_')]) # イベント識別子を列挙

rect=[(0,0),(0,0)] # 矩形描画用

def cv2_win_event(event,x,y,flags,param): # mouse callback function
    print(x,y,flags)
    if event == cv2.EVENT_LBUTTONDOWN:
        param[0]=(x,y)
    elif event == cv2.EVENT_RBUTTONDOWN:
        param[1]=(x,y)

cv2.namedWindow('eventtest', cv2.WINDOW_NORMAL)#第2引数で、ウインドウサイズ変更可能
cv2.setMouseCallback('eventtest',cv2_win_event,rect)
while True:
    frame_img2 = frame_img.flatten().reshape( frame_img.shape )
    cv2.rectangle(frame_img2,rect[0],rect[1],(0,0,255),thickness=5)#矩形描画
    cv2.imshow("eventtest", frame_img2) # ウインドウ表示
    #print(frame_img,  frame_img.shape )
    code = cv2.waitKey(1) 
    if code == 27: break

cv2.destroyAllWindows() # ウインドウを閉じる
cut_img = frame_img[rect[0][1]:rect[1][1], rect[0][0]:rect[1][0] ] # トリミング
cv2.imwrite("opencvevent.jpg",cut_img) # 切り取ったイメージのファイルの保存

OpenCVの顔認識例

物体検出に使われる識別器の1つで、「Haar Cascade」と呼ばれるアルゴリズムのクラスがcv2に組み込まれています。
これは、複数のカスケード分類器をカスケード接続した構成で、各分類器で positive または negative のどちらであるか判定が行われます。
そして、ある分類器で negative と判定された場合はその時点で棄却され、positive と判定された場合は、分類器の出力結果を次の分類器に渡します。

OpenCV では CascadeClassifier クラスでカスケード分類器が提供されています。
この引数にカスケードファイルと呼ばれる.xmlファイルを指定して使います。
この.xmlファイル群は、特徴量がすでに学習済みデータとして提供されています。
以下では、顔 (正面)判定用の「haarcascade_frontalface_default.xml」と 目判定用の「haarcascade_eye.xml」の利用例を示します。
なお、このファイルはpip install opencv-pythonのモジュールインストールで存在するはずです。
ですから、モジュールがあるディレクトリから探すとよいでしょう。

さて、『import モジュール名』 のコードでモジュール名を探して読み込まれますが、どののように探すのでしょうか?
実は、sys.path で得られるリスト内のディレクトリ内を探すようになっているのです
例を以下に示します。

>>> import sys
>>> sys.path ['', 'R:\\Python36', R:\\Python36\\python36.zip', 'R:\\Python36\\DLLs', 'R:\\Python36\\lib', 'R:\\Python36\\lib\\site-packages', 'R:\\Python36\\lib\\site-packages\\win32', 'R:\\Python36\\lib\\site-packages\\win32\\lib', 'R:\\Python36\\lib\\site-packages\\Pythonwin']
>>>

この各ディレクトリ内を、プログラムで探して使ってみます。
所定のディレクトリ内からあるファイルを探す場合の例を示します

import glob
a=glob.glob("R:\\**\\*.xml", recursive=True)
# aに記憶されるパスは見つかったパスのリストになる。無い場合は[] 

これを利用して、顔 (正面)判定用の「haarcascade_frontalface_default.xml」と 目判定用の「haarcascade_eye.xml」を探して、それぞれの判定機のCascadeClassifier クラスオブジェを生成して、 カメラの画面から見つかった顔と目を矩形で描画するプログラム例を示します。

import cv2 # opencvface.py
import sys
import os
import glob # ファイル探索用
face_cascade='haarcascade_frontalface_default.xml'# 顔のHaar Cascade識別器(分類器)
eye_cascade='haarcascade_eye.xml'

# 第一引数のディレクトリパス文字列のリスト内で、第二引数のファイル名を探して、
# 見つかったファイルの絶対パスを返す。(探索は、サブディレクトリ内も行う)
def searchFile( listDir, filename):
    for dirPath in listDir:
        if not os.path.isdir(dirPath): continue
        searhpath=dirPath + os.sep + "**" + os.sep + filename
        #print(searhpath)
        listpath=glob.glob(searhpath, recursive=True)
        if len(listpath)!=0: return listpath[0]
    raise Exception(filename+"が見つからない") 

face_cascade_path = searchFile(sys.path, face_cascade)
eye_cascade_path = searchFile(sys.path, eye_cascade)
print(face_cascade_path,"\n",eye_cascade_path)
# 分類器に、学習済のHaar-like特徴を用いたデータ(xmlファイル)をセット
face_cascade = cv2.CascadeClassifier(face_cascade_path)
eye_cascade = cv2.CascadeClassifier(eye_cascade_path)

camera = cv2.VideoCapture(0)
while True:
    success, frame_img = camera.read() # カメラ画像取得する。
    gray = cv2.cvtColor(frame_img, cv2.COLOR_BGR2GRAY)
    faces = face_cascade.detectMultiScale(gray, scaleFactor=1.1, minNeighbors=5)
    # 上記で顔と判断した矩形[x, y, w, h]の配列を得る。
    # scaleFactorは1.0を超えるで大きいと縮小量で計算が速くなるが見逃しも増える(初期値1.1)
    # minNeighborsが大きいと、見逃しは多いが誤検出は少ない(初期値3)
    for x, y, w, h in faces:
        cv2.rectangle(frame_img, (x, y), (x + w, y + h), (255, 0, 0), 2)
        face = frame_img[y: y + h, x: x + w]
        face_gray = gray[y: y + h, x: x + w]
        eyes = eye_cascade.detectMultiScale(face_gray)
        for (ex, ey, ew, eh) in eyes:
            cv2.rectangle(face, (ex, ey), (ex + ew, ey + eh), (0, 255, 0), 2)
    cv2.imshow('video image', frame_img)
    key = cv2.waitKey(10)
    if key == 27:  # ESCキーで終了
        break

camera.release()
cv2.destroyAllWindows()

cv2.imread("ファイルパス") で得たイメージ内の矩形部をコピーして貼り付ける加工

cv2.imread("ファイルパス") で得たイメージ内の指定矩形部をコピーする関数定義

def get_rect_image(img, x1,y1,x2,y2):
    ''' (x1,y1)は矩形の左上端、(x2,y2)右下端の座標 '''
    return img[y1:y2,x1:x2]

上記get_rect_imageで得たイメージを他のイメージに貼り付ける関数定義

def set_rect_image(img, px1, py1, rect_image):
    # imgの px1, py1 の位置に rect_image を貼り付ける
    sh,sw=img.shape[0],img.shape[1]
    rh,rw=rect_image.shape[0],rect_image.shape[1]
    img[py1:py1+rh, px1:px1+rw]=rect_image

上記関数の利用例

img = cv2.imread( "ume5_426_240.png" )  # イメージファイルの読み取り
cv2.imshow("Capture", img) # 表示
key = cv2.waitKey(3000) # 3 秒待つ

rect_img=get_rect_image(img, x1=168,y1=48,x2=386,y2=310)
set_rect_image(img, px1=0, py1=0, rect_image=rect_img)
cv2.imshow("Capture", img) # 表示
key = cv2.waitKey(3000) # 3 秒待つ
cv2.imwrite( "ume5_426_240_2.png" , img)  # イメージファイルの書き込み

クリック位置にオーブのような明るい円になる点の集合を作る実験

異物検出の検討用に作ったものです。
左下の元画像ファイル(myroom.png)がある時、これを表示してクリック操作で、オーブを描き、右下のような画像を生成するプログラムです。

cv2.imshowで、frame_imgイメージの描画の繰り返しがあります。
これにcv2_win_eventのイベント関数を登録して、操作します。
この関数内で、マウス左クリックでオーブンの中心点を設定し、その点を基準として 右クリックした半径で、オーブを描画させています。
オーブ描画関数は、putOrbです。ここでは正規分布の乱数で、複数の点座標を生成し、 指定半径以内の点をaddPixel関数で明るくする変更をしています。
なお、ESCキーで保存終了です。

import cv2 # opencvevent2.py
import numpy as np

frame_img = cv2.imread("myroom.png")  # 加工対象の画像 
print(frame_img.shape)
h, w , d = frame_img.shape

param={"img":None, "x":0,"y":0} # 描画情報

def addPixel(BGR,val):# 各色の値にvalを加算(BGRの画素に明暗を施す)
    if val >= 0:
        m = map( lambda x : 255 if x+val > 255 else x+val, BGR )
    else:
        m = map( lambda x : 0 if x+val < 0 else x+val, BGR )
    return np.array(list(m), np.uint8)

def putOrb(img, x,y,numb, radius, val=10): # オーブを生成
    ''' numb個の点にvalの明暗を設定 '''
    xs = np.random.normal(x,radius*2.5,numb).astype(np.int)
    ys = np.random.normal(y,radius*2.5,numb).astype(np.int)
    pts = list(zip(xs,ys))
    for px,py in pts:
        if px >= 0 and px < img.shape[1] and py >= 0 and py < img.shape[0]:
            distance = np.sqrt((px-x)**2 + (py-y)**2)
            if distance > radius: continue
            bgr=addPixel(img[py][px], val)
            img[py][px]=bgr
            print(f"px={px:3},py={py:3},{bgr}")

# mouse callback function
def cv2_win_event(event,x,y,flags,param):
    if event == cv2.EVENT_LBUTTONDOWN:# オーブ中心座標指定
        print(x,y,flags)
        param["x"], param["y"] = x,y
    if event == cv2.EVENT_RBUTTONDOWN:# オーブ半径指定で、描画処理
        px, py = param["x"],param["y"]
        radius=np.sqrt((px-x)**2 + (py-y)**2)
        numb = int(5000*radius)
        putOrb(param["img"],px,py,numb=numb,radius=radius,val=2)#オーブ生成

cv2.namedWindow('eventtest', cv2.WINDOW_NORMAL)#第2引数で、サイズ変更可能
cv2.setMouseCallback('eventtest',cv2_win_event,param)
param["img"]=frame_img
while True:  
    cv2.imshow("eventtest", frame_img) # ウインドウ表示
    #print(frame_img,  frame_img.shape )
    code = cv2.waitKey(1) 
    if code == 27: break # ESC で終了

cv2.destroyAllWindows() # ウインドウを閉じる
cv2.imwrite("myroom2.png",frame_img) # 加工したイメージのファイルの保存