画像のモザイク化

タグの編集
投稿者 ギル  (学生) 投稿日時 2021/7/21 18:48:29
visual basicを使っています。授業でやっているだけなのであまり詳しくないのですみません。
 Private Sub Button3_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button3.Click
        SaveFileDialog1.Filter = "画像ファイル(*.jpg *.gif *.bmp)|*.jpg;*.gif;*.bmp"
        If SaveFileDialog1.ShowDialog() = Windows.Forms.DialogResult.OK Then
            PictureBox2.Image.Save(SaveFileDialog1.FileName)     'モザイク化画像ファイル保存
        End If
    End Sub
End Class

このPictureBox2.Image.Save(SaveFileDialog1.FileName)部分でデバックを開始しボタンを使用するとGDI+汎用エラーが出てきたんですがどうすれば正常に戻りますか?

投稿者 魔界の仮面弁士  (社会人) 投稿日時 2021/7/21 20:18:31
> GDI+汎用エラーが出てきた
PictureBox2.Image が Nothing だった場合に出るエラーとは異なることから、
画像の保存処理そのものが失敗しているのだと推察してみました。

たとえば、保存しようとした画像ファイルが現在使用中のファイルだったことで
上書き更新できなかったということはないでしょうか。
事例としては、保存しようとしたファイルが、以前に Image.FromFile メソッドで開いており、
その開いている画像をまだ解放していないままの状態だったとか…。

あるいは、新規ファイルでも保存に失敗するようであれば、
保存先のパスに対して、書き込み可能なアクセス権限を有していなかった、とか。


> どうすれば正常に戻りますか?
ファイルの読み書き時は、アクセス権の問題やディスク容量の枯渇などによって
何かしらのエラーが発生する可能性があります。

そのためファイルへの保存時には必ず、「Try~Catch ステートメント」を併用して
ファイルアクセスに失敗した場合に、別のファイル名を選択させたり、
あるいは保存を中止するなどの処理を組み込むことが望ましいです。
処理を
投稿者 るきお  (社会人) 投稿日時 2021/7/21 20:26:59
ギルさんが掲載されたプログラムをシンプルにコピペして、少し補って実行した限りでは問題なく保存できました。

Button3をクリックするまでに何をやっているかが問題ではないかと思います。

私がうまくできているプログラムを載せます。
Public Class Form1
    Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
        PictureBox1.Image = Image.FromFile("C:\temp\sample.jpg")
    End Sub

    Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click

        Dim result As New Bitmap(400, 400)
        Dim g = Graphics.FromImage(result)

        'PictureBox1の画像を2倍に拡大して、PicutreBox2に表示 
        g.Transform = New Drawing2D.Matrix(2, 0, 0, 2, 1, 1)
        g.DrawImage(PictureBox1.Image, 0, 0)

        PictureBox2.Image = result

    End Sub
    Private Sub Button3_Click(ByVal sender As System.ObjectByVal e As System.EventArgs) Handles Button3.Click

        SaveFileDialog1.Filter = "画像ファイル(*.jpg *.gif *.bmp)|*.jpg;*.gif;*.bmp"
        If SaveFileDialog1.ShowDialog() = Windows.Forms.DialogResult.OK Then
            PictureBox2.Image.Save(SaveFileDialog1.FileName)     'モザイク化画像ファイル保存 
        End If
    End Sub

End Class


Button1 → Button2 → Button3 の順にクリックして正常に保存できています。
.NET 5 で試しました。
投稿者 ギル  (学生) 投稿日時 2021/7/21 22:37:38
Public Class Form1
    Dim mosaic_size_w, mosaic_size_h, r_data(160, 120), g_data(160, 120), b_data(160, 120) As Integer
    Dim R, G, B, R_average, G_average, B_average As Integer
    Dim myColor As Color
    Dim myBMP, myBMPM As New Bitmap(160, 120)   'myBMPMがないと原画像もモザイク化してしまうので
    Dim Coupled As Boolean = True   'モザイクサイズ(幅、高さ)の連動・非運動フラグ
    Dim OpenFileDialog1 As New OpenFileDialog
    Dim SaveFileDialog1 As New OpenFileDialog

    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
        OpenFileDialog1.Filter = "画像ファイル(*.jpg *.gif *.bmp)|*.jpg;*.gif;*.bmp"
        If OpenFileDialog1.ShowDialog() = Windows.Forms.DialogResult.OK Then
            myBMP = Image.FromFile(OpenFileDialog1.FileName)    '原画像ファイル読み込み
            For y As Integer = 0 To PictureBox1.Height - 1
                For x As Integer = 0 To PictureBox1.Width - 1
                    myColor = myBMP.GetPixel(x, y)
                    r_data(x, y) = myColor.R    'Red成分
                    g_data(x, y) = myColor.G    'Green成分
                    b_data(x, y) = myColor.B    'Blue成分
                Next
            Next
            PictureBox1.Image = myBMP
        End If
    End Sub

    Private Sub NumericUpDown1_ValueChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles NumericUpDown1.ValueChanged
        mosaic_size_w = NumericUpDown1.Value
        If Coupled = True Then
            NumericUpDown2.Value = mosaic_size_w
            mosaic_size_h = mosaic_size_w
        End If
    End Sub

    Private Sub NumericUpDown2_ValueChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles NumericUpDown2.ValueChanged
        mosaic_size_h = NumericUpDown2.Value
        If Coupled = True Then
            NumericUpDown1.Value = mosaic_size_h
            mosaic_size_w = mosaic_size_h
        End If
    End Sub

    Private Sub CheckBox1_CheckedChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles CheckBox1.CheckedChanged
        If CheckBox1.Checked = True Then
            Coupled = True
        Else
            Coupled = False
        End If
    End Sub

    Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
        Dim i, j, pixels As Integer
        Debug.WriteLine("mosaic_size_w=" & mosaic_size_w) ' モザイクサイズ(幅)の確認
        Debug.WriteLine("mosaic_size_w=" & mosaic_size_w) ' モザイクサイズ(高さ)の確認
        ' モザイク単位ごとの処理
        For y As Integer = 0 To PictureBox1.Height - 1 Step mosaic_size_h
            For x As Integer = 0 To PictureBox1.Width - 1 Step mosaic_size_w
                R = 0 ' R平均値初期設定
                G = 0 ' G平均値初期設定
                B = 0 ' B平均値初期設定
                For j = 0 To Math.Min(mosaic_size_h, PictureBox1.Height - y) - 1
                    For i = 0 To Math.Min(mosaic_size_w, PictureBox1.Width - x) - 1
                        R = R + r_data(x + i, y + j)
                        G = G + g_data(x + i, y + j)
                        B = B + b_data(x + i, y + j)
                    Next
                Next
                'モザイク単位内の総ピクセル数
                pixels = Math.Min(mosaic_size_w, PictureBox1.Width - x) * Math.Min(mosaic_size_h, PictureBox1.Height - y)
                R_average = Int(R / pixels)
                G_average = Int(G / pixels)
                B_average = Int(B / pixels)
            Next
        Next
        PictureBox2.Image = myBMPM
    End Sub
    Private Sub Button4_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button4.Click
        End ' プログラムの終了
    End Sub
End Class

自分でやったもので全体としてこんな感じでデバックを開始するとpicturebox2でのモザイク画像が表示されないという感じになっています。
投稿者 るきお  (社会人) 投稿日時 2021/7/22 09:03:36
試しに実行してみたのですが、エラーで先に進めず断念しました。

はじめ大きな画像で実行するとButton1_Clickの
r_data(x, y) = myColor.R    'Red成分 
の行で IndexOutOfRangeException の例外

小さな画像でやったら
myColor = myBMP.GetPixel(x, y)
の行で ArgumentOutOfRange の例外

PictureBox1のサイズを160x120にする必要があるのかなとやってみたら
Button1クリックでエラーなく画像が表示されましたが、
Button2クリックの
R_average = Int(R / pixels)
の行で OverflowException の例外

ここで断念しました。
(これ以上時間を使いたくないので)

ギルさんが解決したい問題は何でしょうか?
その問題はどのような操作で発生しますか?

最初にご質問された
PictureBox2.Image.Save(SaveFileDialog1.FileName)     'モザイク化画像ファイル保存
の行も、含まれておらずもうちょっとわかりやすく質問していただけるとお互い時間の節約になるかと思います。
投稿者 るきお  (社会人) 投稿日時 2021/7/22 11:01:38
ちょっと作ってしまいました。
長くなって掲示板の文字制限をオーバーするので投稿は分割します。

Button1クリック後 → Button2 クリックで サイズ10のモザイク化
NumericUpdown1の数字を変更するとそのサイズでモザイク化します。

Option Strict On

Public Class Form1

    Private Sub Button1_Click(ByVal sender As System.ObjectByVal e As System.EventArgs) Handles Button1.Click

        Dim dialog As New OpenFileDialog
        dialog.Filter = "画像ファイル(*.jpg *.gif *.bmp)|*.jpg;*.gif;*.bmp"
        If dialog.ShowDialog() = Windows.Forms.DialogResult.OK Then
            PictureBox1.Image = Image.FromFile(dialog.FileName)
        End If
    End Sub


    Private Sub Button2_Click(ByVal sender As System.ObjectByVal e As System.EventArgs) Handles Button2.Click

        'PictureBox1の画像にモザイクをかけた画像を生成します。 
        'この段階では画像はメモリ上にあり、表示は変化しません。 
        Dim result As Image = Mosaic(PictureBox1.Image, 10)

        '生成した画像をPictureBox2に表示させます。 
        PictureBox2.Image = result
    End Sub

    Private Sub NumericUpDown1_ValueChanged(sender As Object, e As EventArgs) Handles NumericUpDown1.ValueChanged
        Dim result As Image = Mosaic(PictureBox1.Image, CInt(Math.Max(NumericUpDown1.Value, 2)))
        PictureBox2.Image = result
    End Sub

投稿者 るきお  (社会人) 投稿日時 2021/7/22 11:02:27
続き

Private Function Mosaic(sourceImage As Image, mosaicSize As IntegerAs Image

        '▼色情報の取得 
        'sourceImageの画像の色情報を配列 rgbBits にコピーします。 
        Dim bmp As Bitmap = CType(sourceImage.Clone, Bitmap)

        Dim bmpData As Imaging.BitmapData = bmp.LockBits(
            New Rectangle(0, 0, bmp.Width, bmp.Height),
            Imaging.ImageLockMode.ReadOnly,
            bmp.PixelFormat)

        Dim bitCount As Integer = Math.Abs(bmpData.Stride) * bmp.Height
        Dim rgbBits(bitCount - 1) As Byte

        'rgbBitsは 赤・青・緑の情報を持つので、画像のピクセル数 × 3 の数の長さになります。 
        System.Runtime.InteropServices.Marshal.Copy(bmpData.Scan0, rgbBits, 0, bitCount)


        '縦・横いくつのモザイクに分割されるか計算します。 
        Dim mosaicCountX As Integer = CInt(Math.Ceiling(bmp.Width / mosaicSize))
        Dim mosaicCountY As Integer = CInt(Math.Ceiling(bmp.Height / mosaicSize))


        '▼モザイクごとに色を設定 
        For mosaicIndexY = 0 To mosaicCountY - 1
            For mosaicIndexX = 0 To mosaicCountX - 1

                '現在のモザイクの画像上の座標 
                Dim pointLeft As Integer = mosaicIndexX * mosaicSize
                Dim pointRight As Integer = pointLeft + mosaicSize - 1
                Dim pointTop As Integer = mosaicIndexY * mosaicSize
                Dim pointBottom As Integer = pointTop + mosaicSize - 1

                Dim sumR As Integer = 0
                Dim sumG As Integer = 0
                Dim sumB As Integer = 0

                'このモザイク内のすべての色を赤・青・緑それぞれで合計します。 
                For pointScanY As Integer = pointTop To pointBottom

                    'ここでの処理は計算は1行ごとに行います。(つまりモザイクのサイズが20であればこのForは20回実行されます。) 
                    'このモザイクの現在の行の色情報が配列 rgbBitsのどの位置にあるか計算します。 
                    Dim bitStartIndex As Integer = pointScanY * bmpData.Stride + pointLeft * 3
                    Dim bitEndIndex As Integer = pointScanY * bmpData.Stride + pointRight * 3

                    For bitIndex As Integer = bitStartIndex To bitEndIndex Step 3
                        If rgbBits.Length > bitIndex Then
                            sumR += rgbBits(bitIndex)
                            sumG += rgbBits(bitIndex + 1)
                            sumB += rgbBits(bitIndex + 2)
                        End If
                    Next
                Next

                'このモザイクの赤・青・緑の平均値を算出します。 
                Dim aveR As Double = sumR / (mosaicSize * mosaicSize)
                Dim aveG As Double = sumG / (mosaicSize * mosaicSize)
                Dim aveB As Double = sumB / (mosaicSize * mosaicSize)


                '算出した平均値をこのモザイク内に等しくセットします。 
                For pointScanY As Integer = pointTop To pointBottom
                    Dim bitStartIndex As Integer = pointScanY * bmpData.Stride + pointLeft * 3
                    Dim bitEndIndex As Integer = pointScanY * bmpData.Stride + pointRight * 3

                    For bitIndex As Integer = bitStartIndex To bitEndIndex Step 3
                        If rgbBits.Length > bitIndex Then
                            rgbBits(bitIndex) = CByte(aveR)
                            rgbBits(bitIndex + 1) = CByte(aveG)
                            rgbBits(bitIndex + 2) = CByte(aveB)
                        End If
                    Next
                Next

            Next

        Next

        '▼色情報の書き込み 
        '算出した色情報を画像に書き込みます。 
        System.Runtime.InteropServices.Marshal.Copy(rgbBits, 0, bmpData.Scan0, bitCount)
        bmp.UnlockBits(bmpData)

        '▼画像の作成(メモリ上で) 
        '新しい色情報を画像 resultImage に書き込みます。 
        Dim resultImage As New Bitmap(bmp.Width, bmp.Height)
        Dim g As Graphics = Graphics.FromImage(resultImage)

        g.DrawImage(bmp, 0, 0)


        Return resultImage

    End Function

End Class
投稿者 ギル  (学生) 投稿日時 2021/7/22 15:55:58
るきおさんのプログラムでモザイク化できたのですが出来ればモザイク画像がズーム?されて表示されるのでそのままのサイズでの表示は出来ないのでしょうか?
投稿者 るきお  (社会人) 投稿日時 2021/7/22 21:50:14
私が試している限りでは画像の大きさが変わるということはありません。
PictureBoxになにかプロパティを設定しているとか、別の処理が介在しているとか、何かありませんか?

新規にWindowsフォームアプリケーションを作成し、Button1, Button2, PictureBox1, PictureBox2, NumericUpDown1 を貼り付けて、プログラムを貼り付けて実行すると同じサイズで表示されるということはないでしょうか。