質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

91.37%

  • Python

    3793questions

    Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

  • OpenCV

    614questions

    OpenCV(オープンソースコンピュータービジョン)は、1999年にインテルが開発・公開したオープンソースのコンピュータビジョン向けのクロスプラットフォームライブラリです。

Saliency Mapの画像を保存したい

解決済

回答 1

投稿 2017/11/23 00:52 ・編集 2017/11/23 00:54

  • 評価
  • クリップ 0
  • VIEW 54

upp

score 4

前提・実現したいこと

こんにちは。
https://github.com/akisato-/pySaliencyMap
上記のサイトで公開されている、
入力した画像をSaliencyMap化するソースコード(main.pyとpySaliencyMap.py)に関して質問させていただきます。 

SaliencyMapを作成するにあたって、その過程における画像を保存して仕組みを理解したいと考えております。

以下のソースコードにおける、ICM、CCM、OCMの三つをcv2.imshowしましたが、真っ黒な画像が表示されてしまいました。

それぞれ、色顕著、輝度顕著、方向顕著を示しているので、
下記のような画像が得られると予想したのですが、
解決方法はございますでしょうか。
https://www.dropbox.com/s/02ndfntnjtjlkm9/saliency.png?dl=0

該当のソースコード

#文字数制限を超えてしまうため、pySaliencyMap.pyの前半部分を省略して掲載しております。
#また、注目していただきたいところを示してあります.

# Name: pySaliencyMap.py
import cv2
import numpy as np
import pySaliencyMapDefs
#省略あり
    # feature maps
    ## constructing a Gaussian pyramid
    def FMCreateGaussianPyr(self, src):
        dst = list()
        dst.append(src)
        for i in range(1,9):
            nowdst = cv2.pyrDown(dst[i-1])
            dst.append(nowdst)
        return dst
    ## taking center-surround differences
    def FMCenterSurroundDiff(self, GaussianMaps):
        dst = list()
        for s in range(2,5):
            now_size = GaussianMaps[s].shape
            now_size = (now_size[1], now_size[0])  ## (width, height)
            tmp = cv2.resize(GaussianMaps[s+3], now_size, interpolation=cv2.INTER_LINEAR)
            nowdst = cv2.absdiff(GaussianMaps[s], tmp)
            dst.append(nowdst)
            tmp = cv2.resize(GaussianMaps[s+4], now_size, interpolation=cv2.INTER_LINEAR)
            nowdst = cv2.absdiff(GaussianMaps[s], tmp)
            dst.append(nowdst)
        return dst
    ## constructing a Gaussian pyramid + taking center-surround differences
    def FMGaussianPyrCSD(self, src):
        GaussianMaps = self.FMCreateGaussianPyr(src)
        dst = self.FMCenterSurroundDiff(GaussianMaps)
        return dst
    ## intensity feature maps
    def IFMGetFM(self, I):
        return self.FMGaussianPyrCSD(I)
    ## color feature maps
    def CFMGetFM(self, R, G, B):
        # max(R,G,B)
        tmp1 = cv2.max(R, G)
        RGBMax = cv2.max(B, tmp1)
        RGBMax[RGBMax <= 0] = 0.0001    # prevent dividing by 0
        # min(R,G)
        RGMin = cv2.min(R, G)
        # RG = (R-G)/max(R,G,B)
        RG = (R - G) / RGBMax
        # BY = (B-min(R,G)/max(R,G,B)
        BY = (B - RGMin) / RGBMax
        # clamp nagative values to 0
        RG[RG < 0] = 0
        BY[BY < 0] = 0
        # obtain feature maps in the same way as intensity
        RGFM = self.FMGaussianPyrCSD(RG)
        BYFM = self.FMGaussianPyrCSD(BY)
        # return
        return RGFM, BYFM
    ## orientation feature maps
    def OFMGetFM(self, src):
        # creating a Gaussian pyramid
        GaussianI = self.FMCreateGaussianPyr(src)
        # convoluting a Gabor filter with an intensity image to extract oriemtation features
        GaborOutput0   = [ np.empty((1,1)), np.empty((1,1)) ]  # dummy data: any kinds of np.array()s are OK
        GaborOutput45  = [ np.empty((1,1)), np.empty((1,1)) ]
        GaborOutput90  = [ np.empty((1,1)), np.empty((1,1)) ]
        GaborOutput135 = [ np.empty((1,1)), np.empty((1,1)) ]
        for j in range(2,9):
            GaborOutput0.append(   cv2.filter2D(GaussianI[j], cv2.CV_32F, self.GaborKernel0) )
            GaborOutput45.append(  cv2.filter2D(GaussianI[j], cv2.CV_32F, self.GaborKernel45) )
            GaborOutput90.append(  cv2.filter2D(GaussianI[j], cv2.CV_32F, self.GaborKernel90) )
            GaborOutput135.append( cv2.filter2D(GaussianI[j], cv2.CV_32F, self.GaborKernel135) )
        # calculating center-surround differences for every oriantation
        CSD0   = self.FMCenterSurroundDiff(GaborOutput0)
        CSD45  = self.FMCenterSurroundDiff(GaborOutput45)
        CSD90  = self.FMCenterSurroundDiff(GaborOutput90)
        CSD135 = self.FMCenterSurroundDiff(GaborOutput135)
        # concatenate
        dst = list(CSD0)
        dst.extend(CSD45)
        dst.extend(CSD90)
        dst.extend(CSD135)
        # return
        return dst
    ## motion feature maps
    def MFMGetFM(self, src):
        # convert scale
        I8U = np.uint8(255 * src)
        cv2.waitKey(10)
        # calculating optical flows
        if self.prev_frame is not None:
            farne_pyr_scale= pySaliencyMapDefs.farne_pyr_scale
            farne_levels = pySaliencyMapDefs.farne_levels
            farne_winsize = pySaliencyMapDefs.farne_winsize
            farne_iterations = pySaliencyMapDefs.farne_iterations
            farne_poly_n = pySaliencyMapDefs.farne_poly_n
            farne_poly_sigma = pySaliencyMapDefs.farne_poly_sigma
            farne_flags = pySaliencyMapDefs.farne_flags
            flow = cv2.calcOpticalFlowFarneback(\
                prev = self.prev_frame, \
                next = I8U, \
                pyr_scale = farne_pyr_scale, \
                levels = farne_levels, \
                winsize = farne_winsize, \
                iterations = farne_iterations, \
                poly_n = farne_poly_n, \
                poly_sigma = farne_poly_sigma, \
                flags = farne_flags, \
                flow = None \
            )
            flowx = flow[...,0]
            flowy = flow[...,1]
        else:
            flowx = np.zeros(I8U.shape)
            flowy = np.zeros(I8U.shape)
        # create Gaussian pyramids
        dst_x = self.FMGaussianPyrCSD(flowx)
        dst_y = self.FMGaussianPyrCSD(flowy)
        # update the current frame
        self.prev_frame = np.uint8(I8U)
        # return
        return dst_x, dst_y

    # conspicuity maps
    ## standard range normalization
    def SMRangeNormalize(self, src):
        minn, maxx, dummy1, dummy2 = cv2.minMaxLoc(src)
        if maxx!=minn:
            dst = src/(maxx-minn) + minn/(minn-maxx)
        else:
            dst = src - minn
        return dst
    ## computing an average of local maxima
    def SMAvgLocalMax(self, src):
        # size
        stepsize = pySaliencyMapDefs.default_step_local
        width = src.shape[1]
        height = src.shape[0]
        # find local maxima
        numlocal = 0
        lmaxmean = 0
        for y in range(0, height-stepsize, stepsize):
            for x in range(0, width-stepsize, stepsize):
                localimg = src[y:y+stepsize, x:x+stepsize]
                lmin, lmax, dummy1, dummy2 = cv2.minMaxLoc(localimg)
                lmaxmean += lmax
                numlocal += 1
        # averaging over all the local regions
        return lmaxmean / numlocal
    ## normalization specific for the saliency map model
    def SMNormalization(self, src):
        dst = self.SMRangeNormalize(src)
        lmaxmean = self.SMAvgLocalMax(dst)
        normcoeff = (1-lmaxmean)*(1-lmaxmean)
        return dst * normcoeff
    ## normalizing feature maps
    def normalizeFeatureMaps(self, FM):
        NFM = list()
        for i in range(0,6):
            normalizedImage = self.SMNormalization(FM[i])
            nownfm = cv2.resize(normalizedImage, (self.width, self.height), interpolation=cv2.INTER_LINEAR)
            NFM.append(nownfm)
        return NFM
    ## intensity conspicuity map
    def ICMGetCM(self, IFM):
        NIFM = self.normalizeFeatureMaps(IFM)
        ICM = sum(NIFM)
        return ICM
    ## color conspicuity map
    def CCMGetCM(self, CFM_RG, CFM_BY):
        # extracting a conspicuity map for every color opponent pair
        CCM_RG = self.ICMGetCM(CFM_RG)
        CCM_BY = self.ICMGetCM(CFM_BY)
        # merge
        CCM = CCM_RG + CCM_BY
        # return
        return CCM
    ## orientation conspicuity map
    def OCMGetCM(self, OFM):
        OCM = np.zeros((self.height, self.width))
        for i in range (0,4):
            # slicing
            nowofm = OFM[i*6:(i+1)*6]  # angle = i*45
            # extracting a conspicuity map for every angle
            NOFM = self.ICMGetCM(nowofm)
            # normalize
            NOFM2 = self.SMNormalization(NOFM)
            # accumulate
            OCM += NOFM2
        return OCM
    ## motion conspicuity map
    def MCMGetCM(self, MFM_X, MFM_Y):
        return self.CCMGetCM(MFM_X, MFM_Y)

    # core
    def SMGetSM(self, src):
        # definitions
        size = src.shape
        width  = size[1]
        height = size[0]
        # check
#        if(width != self.width or height != self.height):
#            sys.exit("size mismatch")
        # extracting individual color channels
        R, G, B, I = self.SMExtractRGBI(src)
        # extracting feature maps
        IFM = self.IFMGetFM(I)
        CFM_RG, CFM_BY = self.CFMGetFM(R, G, B)
        OFM = self.OFMGetFM(I)
        MFM_X, MFM_Y = self.MFMGetFM(I)
###################################################################
        # extracting conspicuity maps
        ICM = self.ICMGetCM(IFM)
        cv2.imshow('ICM.jpg',ICM)
        CCM = self.CCMGetCM(CFM_RG, CFM_BY)
        cv2.imshow('CCM.jpg',CCM)
        OCM = self.OCMGetCM(OFM)
        cv2.imshow('OCM.jpg',OCM)
        MCM = self.MCMGetCM(MFM_X, MFM_Y)
###################################################################
        # adding all the conspicuity maps to form a saliency map
        wi = pySaliencyMapDefs.weight_intensity
        wc = pySaliencyMapDefs.weight_color
        wo = pySaliencyMapDefs.weight_orientation
        wm = pySaliencyMapDefs.weight_motion
        SMMat = wi*ICM + wc*CCM + wo*OCM + wm*MCM
        # normalize
        normalizedSM = self.SMRangeNormalize(SMMat)
        normalizedSM2 = normalizedSM.astype(np.float32)
        smoothedSM = cv2.bilateralFilter(normalizedSM2, 7, 3, 1.55)
        self.SM = cv2.resize(smoothedSM, (width,height), interpolation=cv2.INTER_NEAREST)
        # return
        return self.SM

試したこと

cv2.imshowもcv2.imwriteと同じく、8bitのみしか使用できないのかとも考え
ICM = ICM.astype(np.uint8)
を使って変換しましたが、結果は変わりませんでした。

変換前は、float32でした。

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 1

checkベストアンサー

+1

以下、表示に関する部分(動作確認)

■cv2.imshow
少なくともWEBCAMで動かしたときには、投稿内容で問題なく動くことが確認できました。
なので、cv2.imshowまでは問題ないですね。

・動作確認方法
要旨:  pythonを使って画像や動画から「意味ありげな部分」を強調してくり抜くアルゴリズム
検証方法:以下の表に沿ってファイルを用意した後にWEBCAMを繋ぎ、main_webcam.pyを起動
DL元:  akisato-さん@github
必要なファイル:

pythonファイル 説明 備考
main_webcam.py 起動に必要な本体 def FMCreateGaussianPyr(略)以降を投稿通りにする
pySaliencyMap.py SaliencyMapのコア -
pySaliencyMapDefs.py フィルタの定義 -

以下、保存に関する部分

■cv2.write
が、cv2.imwriteそのまま保存すると画像が真っ黒になりました。

・対策

原因:  print(OCM.dtype)->float64だと確認できました。
対策:  保存したい画像*255のあとに、uint8に変換
※細かいことは以前の質疑と同じ感じです

中身 最小 最大
float64 64bit浮動小数点数 0.000... 1.000...
uint8 符号なし整数 0 255

実際のコード:
とりあえずOCMについて書くと以下の感じです。OCMをCCMやICMに変えれば全て行けますね。

OCM = OCM * 255
OCM = OCM.astype(np.uint8)
cv2.imwrite('OCM.jpg',OCM)

投稿 2017/11/23 07:15

編集 2017/11/23 07:26

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2017/11/25 00:35

    slashさん、ご回答ありがとうございます。
    大変お世話になっております。

    前回に引き続き、データ型が問題となっていたのですね。
    勉強をしたと思っておりましたが、
    プログラムが起動したためにOpenCVの設定が原因だと難しく考え、失念しておりました。
    プログラミングは、こういった基本に立ち返ることが大切なのだと学ばせていただきました。

    ご指導いただき、本当にありがとうございました。

    キャンセル

  • 2017/11/25 01:07 編集

    いえいえ。

    画像処理はあんまり難しく考えるより、画像を見て数値ではなく感性で捉えて、画像が「黒いなー」、paintに突っ込んで塗りつぶすと「真っ黒一色ではなく、真っ黒に近い複数の色からできてるなー」、「floatの0,1問題かなぁ」のように臭い(?)で感じとって進めた方が分かりやすいと思います。

    キャンセル

  • 2017/11/25 01:14

    連続の投稿申し訳ございません…。
    ベストアンサーを付けさせていただいた際は、OCMのみを試して実行結果を見て満足していたのですが、
    CCMとICMの場合、同様の手段では画像を得ることはできませんでした。

    データ型を解析してみたところ、
    ICM…「float32,float32,float32,float32,float32,float32,float32,float64,float64」
    CCM…
    「float32,float64」
    とループのたびにデータ型が変化しているようです。

    解決方法はございますでしょうか。
    何度もご質問してしまい、申し訳ございません。

    キャンセル

  • 2017/11/25 01:22

    動かしていないので何とも言えませんが、とりあえずfloat32/64の区別なしにuint8にしてはいかがでしょうか?直観ではいずれも0~1になると思うので、(float*255)-->uint8で行けそうな気がします。取り急ぎダメだったらfloat32/64の型を調べて、型に沿って処理を変えるかだと思います。

    キャンセル

  • 2017/11/25 01:23 編集

    CCM = CCM.astype(np.float64)
    CCM = CCM * 255
    CCM = CCM.astype(np.uint8)
    といった形に型を無理やり変化させてみたのですが、やはり真っ黒の画像が出現してしまいました。

    感性や匂いでとらえる…
    プログラミングというものに慣れる必要がありますね。
    slashさんのわかりやすい的確なアドバイスは、たゆまぬ努力と経験によって得られたのだと感じます。

    キャンセル

  • 2017/11/25 01:40

    こちらでも試してみますね。少し時間をください(日曜日くらいまでかかるかもしれません)。

    > たゆまぬ努力と経験
    そんなかっこいいものではo_0

    キャンセル

  • 2017/11/25 01:46

    親身に相談にのってくださり、大変感謝しております。
    当方もいろいろな試行錯誤を繰り返し、進展があればご報告いたします。

    よろしくお願いいたします!!

    キャンセル

  • 2017/11/26 11:06

    ざーっと試しました。

    cv2.equalizeHist()-->階調がつぶれる
    ○○*64の後にuint8化--->階調がつぶれる(?)振り切って黒くなる

    正しいやり方がいまいち浮いて来ませんが、たぶんOpenCVの乗算処理(255で打ち切り)でそれっぽいものは出せそうな気がします。あとはガンマ補正(https://teratail.com/questions/88777の私の回答を見てください)でしょうか。
    取り急ぎ連絡まで。

    キャンセル

  • 2017/11/26 11:08

    https://www.researchgate.net/post/how_to_save_an_image_of_type_float32_in_opencv
    のPeter Bankheadさんの以下が使えるかもしれません(floatをイメージに)
    >Try this:
    >http://www.lfd.uci.edu/~gohlke/code/tifffile.py.html
    >You can use it directly, or as a plugin to scikit-image:
    >http://scikit-image.org/docs/0.10.x/api/skimage.io.html#imsave
    >I did it the latter way, with something like:
    >if image.dtype == 'float32':
    > skimage.io.imsave(f_path, image, plugin="tifffile", **kwargs)

    キャンセル

  • 2017/11/28 01:24

    slashさん、お世話になっております。uppです。
    ご連絡が遅れてしまい申し訳ございません。

    本日、教えていただいたPeter Bankheadさんの方法を試した結果、
    CCMもICMも下記のような理想とする画像を得ることができました。
    https://www.dropbox.com/s/f9t2p3c0k1wscnm/rated%20tennis%20hard%20CCM.jpg?dl=0
    また、このプログラムを解析していく中でデータ型と関数のルールを改めて振り返ることができました。
    ガンマ補正に関してもグラフ込みで説明されていて、理解が進みやすく感じました。

    この度は、非常に親身になって問題解決に協力していただき誠にありがとうございます。
    また、機会がございましたらどうかよろしくお願いいたします。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

ただいまの回答率

91.37%

関連した質問

同じタグがついた質問を見る

  • Python

    3793questions

    Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

  • OpenCV

    614questions

    OpenCV(オープンソースコンピュータービジョン)は、1999年にインテルが開発・公開したオープンソースのコンピュータビジョン向けのクロスプラットフォームライブラリです。