none
読み込めない「Jpeg」ファイルについて RRS feed

  • 質問

  • 「Jpeg」ファイルが壊れているか否かを判断し、正常なファイルだけを次のようにして読み込みました。

    Dim Photo As New List(Of String)
    Dim dmy As Image = Nothing
    For Each fi As IO.FileInfo In files
        Try
            dmy = Image.FromFile(fi.FullName)
            Photo.Add(fi.FullName)
            dmy.Dispose()
        Catch ex As Exception
            'エラーをスルー
        End Try
    Next

    しかし、これですと「Jpeg」のファイル数が多いと非常に時間がかかります。
    エクスプローラで見てみると壊れて読み込めない「jpg」ファイルのサイズは「0」バイトとなっています。
    これを利用してファイルサイズが「0」バイトでないファイルを読み込むようにしましたが、これは邪道なのでしょうか。言い換えると、ファイルサイズが「0」バイトでない場合でも読み込めないファイルはあるのでしょうか。
    このフォーラムで質問すべき内容ではないかもしれませんが、宜しくお願いします。

    2018年9月10日 23:35

回答

  • 定義次第ではないでしょうか。

    元の質問文では「読み込めない」を1つの判断基準とされているようですので、GDI+ のデコーダー(Image.FromFile)を試すという手段をとられています。
    この手段自体(GDI+ の JPEG デコーダーが読めるか否か)が定義になってしまうと、そのデコーダーの実装依存となってしまうので、ファイルサイズが特定よりも小さいもので事前に枝刈りする以外の高速化方法はないとなってしまいますね。
    (この枝刈りも言うほどの効果はないと思われますが…)
    この定義を変えられない場合、CPU がボトルネックであれば並列化を検討する、ディスクアクセスがボトルネックであれば改善策なしになります。

    定義を変えて良いなら、JPEG のタグを読んでみて、ある程度ブロック構造が正しければ OK とするなど、別の実装をしてみるとかですね。こうすれば、JPEG のデコード処理を省けるので、構造的な正しさの検証はもっと高速にできそうな気はします。

    // ShiroYuki_Mot さんが提起されている部分まで気にし始めると、高速化余地はないどころか、実現性が厳しい領域になってくるので妥協は必要だと思います。(^^;

    • 回答としてマーク nebokken 2018年9月12日 5:34
    2018年9月11日 13:30
    モデレータ

すべての返信

  • > これを利用してファイルサイズが「0」バイトでないファイルを読み込むようにしましたが、これは邪道なのでしょうか。

    「壊れている」=「0 バイト」ではなさそうなので、「邪道」かどうかは置いといても、「壊れている」ことを判断するには条件として不足かもしれません。

    「壊れている」ことを判断するのに、その画像ファイルから Bitmap を作成できるか否かを調べるという手段があるそうです。

    Read image and determine if its corrupt C#
    https://stackoverflow.com/questions/8846654/read-image-and-determine-if-its-corrupt-c-sharp

    自分は試したことはなく、ググって調べただけなので保証の限りではないですが。

    C# jpeg file corruption judge などをキーワードにググると他にもいろいろ参考になりそうな記事がヒットしますので、自分でも調べてみてください。

    【追伸】

    今頃気づいて何ですが、質問者さんのコードの dmy = Image.FromFile(fi.FullName) と、紹介した記事のコードの var bmp = new Bitmap(filename) はほぼ同じ事のようですね。見落とし、すみません



    • 編集済み SurferOnWww 2018年9月11日 1:15 追伸を追記
    2018年9月11日 1:00
  • ファイル数が多いなら、それを半分ずつ(あるいは 3分割、4 分割でも良いですが)のリストに分けてから、マルチスレッドで手分けして解析させてみたら、多少は早くならないでしょうか。

    ファイルサイズが「0」バイトでないファイルを読み込むようにしましたが、これは邪道なのでしょうか。

    「0 バイトではないが、正しい JPEG 形式ではないファイル」は幾らでもありますが、ファイル自体のサイズが 0 であるなら、それは正しい画像とは言えませんね。ファイルサイズではなく「ディスク上のサイズが 0 バイト」だと話が変わってきますが。

    0バイト

    2018年9月11日 1:35
  • nebokken さま よろしく。

    本題から離れますが、 こんな壊れた jpg ファイルだってあります、と言う例を。

    > 「Jpeg」ファイルが壊れているか否かを判断し

    これは大変に難しい問題を含みます。

    読み込めても、壊れているファイルが稀に出ます。
    以下の例は、画像取り込み時にメディアトラブルで欠損が生じた、写真的には「壊れたファイル」なのですが、
    ファイル冒頭のメタデータやサムネイル部分は一部生きているのに、画像本体のデータが破壊されたものです。
    画像表示サイズを変えると、縞模様がガラッと変わります。(サムネイルも破損状態 湖と山の写真だったもの)
    ファイル自体は勿論 0 バイトではなく、正規のサイズになっています。 jpg としてのファイル構造迄は合っているのですが、肝心の画像データは滅茶苦茶な例です。
    類似のケースで良くあるのが、サムネイルが真面で本体が壊れたケースも多い様です。
    これをご覧になってどう思われますか?。

    • 編集済み ShiroYuki_Mot 2018年9月11日 13:19 写真の内容を追記
    2018年9月11日 13:08
  • 定義次第ではないでしょうか。

    元の質問文では「読み込めない」を1つの判断基準とされているようですので、GDI+ のデコーダー(Image.FromFile)を試すという手段をとられています。
    この手段自体(GDI+ の JPEG デコーダーが読めるか否か)が定義になってしまうと、そのデコーダーの実装依存となってしまうので、ファイルサイズが特定よりも小さいもので事前に枝刈りする以外の高速化方法はないとなってしまいますね。
    (この枝刈りも言うほどの効果はないと思われますが…)
    この定義を変えられない場合、CPU がボトルネックであれば並列化を検討する、ディスクアクセスがボトルネックであれば改善策なしになります。

    定義を変えて良いなら、JPEG のタグを読んでみて、ある程度ブロック構造が正しければ OK とするなど、別の実装をしてみるとかですね。こうすれば、JPEG のデコード処理を省けるので、構造的な正しさの検証はもっと高速にできそうな気はします。

    // ShiroYuki_Mot さんが提起されている部分まで気にし始めると、高速化余地はないどころか、実現性が厳しい領域になってくるので妥協は必要だと思います。(^^;

    • 回答としてマーク nebokken 2018年9月12日 5:34
    2018年9月11日 13:30
    モデレータ
  • Azulean さま 拝見しました。

    ソフトウェア的には仰られる通りです。 私も、タグやファイル構造を見るのがひとつの正解だと思います。

    実際には、QA サイト等で散見される 写真の 壊れたファイルの質問 は上に例示したケースが多々見られ、こんな事もあるので、「頭の片隅に置いて置いてね」的な返信になります。  この場合、タグが生きているのが実に嫌らしい点になりますね。 「サムネイルは真面なのにソフトで開けない 開くと違ったものが現れる」と言うのも良くあるトラブルの様ですよ。

    • 編集済み ShiroYuki_Mot 2018年9月11日 14:15 言い回し変更
    2018年9月11日 14:10
  • 画像がめちゃくちゃでも読み込むことができれば良いです。
    「Jpeg」のファイル構造を調べてみると、ファイルの先頭2バイトは「FFD8」ファイルの最後2バイトは「FFD9」
    それを調べるとできないでしょうかね。
    Windowsは一番最初は何を持って読み込めると判断するのでしょうか。
    2018年9月12日 1:28
  • > 「Jpeg」のファイル構造を調べてみると、ファイルの先頭2バイトは「FFD8」ファイルの最後2バイトは「FFD9」
    > それを調べるとできないでしょうかね。

    最初の私のレスで「C# jpeg file corruption judge などをキーワードにググると他にもいろいろ参考になりそうな記事がヒットします」と書きましたが、やってみましたか?

    ヒットする記事の中に上記に類する調べ方の記事がありますが読みましたか? それらでは目的を果たせませんか?
    2018年9月12日 1:44
  • 申し訳ありません。英語が苦手なのでスルーしてしまいました。もう少し、時間がかかりそうです。
    2018年9月12日 2:04
  • これでどうでしょう。

    ファイル総数に対する破損率にもよるとは思いますが、手元の環境で実験した限りでは、画像形式の判定処理を追加しているにも関わらず、元の処理よりも高速化されました。

    '画像形式判定用
    Dim JpegId = System.Drawing.Imaging.ImageFormat.Jpeg.Guid
    
    '少なくとも 6 バイト以下の JPEG はありえないので除外
    Dim imgFiles As String() = (From fi As System.IO.FileInfo In files Where fi.Length > 6 Select fi.FullName).ToArray()
    
    '並列処理用に分割
    Dim part = System.Collections.Concurrent.Partitioner.Create(0, imgFiles.Length)
    
    '正しく読み込めたら True にする
    Dim results(imgFiles.Length - 1) As Boolean
    
    '並列処理
    Parallel.ForEach(
        part,
        Sub(range, loopState)
            For i As Integer = range.Item1 To range.Item2 - 1
                Try
                    Using jpg = Image.FromFile(imgFiles(i))
                        '正しく読み込めても、それが JPEG 形式で無いなら False
                        results(i) = (jpg.RawFormat.Guid = JpegId)
                    End Using
                Catch
                    results(i) = False
                End Try
            Next
        End Sub)
    
    '読み込みに成功した JPEG ファイル名のみを取得
    Dim Photo As List(Of String) = imgFiles.Where(Function(s, i) results(i)).ToList()
    2018年9月12日 5:29
  • ファイルの先頭2バイトと最後の2バイトを調べる方が格段に速いことが分かりました。
    3000の「Jpeg」ファイル
    Image.FromFile()は時間がかかるのでBackGroundWorkerで125秒
    先頭2バイトと最後の2バイトを調べる方法で3秒ほどでした。

    それで、結局ファイルサイズが0のときと、先頭2バイトと最後の2バイトが正しくないなら間違いなく読み込めないと思うのでその程度にしたいと思います。

    何とか目的を達成できそうです。みなさん、どうもありがとうございました。上記では不十分だとは思いますが、ファイル構造という観点からAzuleanさんに回答マークを付けさせてください。

    2018年9月12日 5:32
  • 返信時刻が、魔界の仮面弁士さんと被ってしまいました。試してみたいと思います。ありがとうございます。
    2018年9月12日 5:37