画像のモザイク化 への返答
投稿で使用できる特殊コードの説明。(別タブで開きます。)
以下の返答は逆順(新しい順)に並んでいます。
投稿者 ギル  (学生)
投稿日時
2021/7/22 15:55:58
るきおさんのプログラムでモザイク化できたのですが出来ればモザイク画像がズーム?されて表示されるのでそのままのサイズでの表示は出来ないのでしょうか?
投稿者 るきお  (社会人)
投稿日時
2021/7/22 11:02:27
続き
Private Function Mosaic(sourceImage As Image, mosaicSize As Integer) As 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 11:01:38
ちょっと作ってしまいました。
長くなって掲示板の文字制限をオーバーするので投稿は分割します。
Button1クリック後 → Button2 クリックで サイズ10のモザイク化
NumericUpdown1の数字を変更するとそのサイズでモザイク化します。
長くなって掲示板の文字制限をオーバーするので投稿は分割します。
Button1クリック後 → Button2 クリックで サイズ10のモザイク化
NumericUpdown1の数字を変更するとそのサイズでモザイク化します。
Option Strict On
Public Class Form1
Private Sub Button1_Click(ByVal sender As System.Object, ByVal 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.Object, ByVal 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 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) 'モザイク化画像ファイル保存
の行も、含まれておらずもうちょっとわかりやすく質問していただけるとお互い時間の節約になるかと思います。
はじめ大きな画像で実行すると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/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でのモザイク画像が表示されないという感じになっています。
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/21 20:26:59
ギルさんが掲載されたプログラムをシンプルにコピペして、少し補って実行した限りでは問題なく保存できました。
Button3をクリックするまでに何をやっているかが問題ではないかと思います。
私がうまくできているプログラムを載せます。
Button1 → Button2 → Button3 の順にクリックして正常に保存できています。
.NET 5 で試しました。
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.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
Button1 → Button2 → Button3 の順にクリックして正常に保存できています。
.NET 5 で試しました。
投稿者 魔界の仮面弁士  (社会人)
投稿日時
2021/7/21 20:18:31
> GDI+汎用エラーが出てきた
PictureBox2.Image が Nothing だった場合に出るエラーとは異なることから、
画像の保存処理そのものが失敗しているのだと推察してみました。
たとえば、保存しようとした画像ファイルが現在使用中のファイルだったことで
上書き更新できなかったということはないでしょうか。
事例としては、保存しようとしたファイルが、以前に Image.FromFile メソッドで開いており、
その開いている画像をまだ解放していないままの状態だったとか…。
あるいは、新規ファイルでも保存に失敗するようであれば、
保存先のパスに対して、書き込み可能なアクセス権限を有していなかった、とか。
> どうすれば正常に戻りますか?
ファイルの読み書き時は、アクセス権の問題やディスク容量の枯渇などによって
何かしらのエラーが発生する可能性があります。
そのためファイルへの保存時には必ず、「Try~Catch ステートメント」を併用して
ファイルアクセスに失敗した場合に、別のファイル名を選択させたり、
あるいは保存を中止するなどの処理を組み込むことが望ましいです。
処理を
PictureBox2.Image が Nothing だった場合に出るエラーとは異なることから、
画像の保存処理そのものが失敗しているのだと推察してみました。
たとえば、保存しようとした画像ファイルが現在使用中のファイルだったことで
上書き更新できなかったということはないでしょうか。
事例としては、保存しようとしたファイルが、以前に Image.FromFile メソッドで開いており、
その開いている画像をまだ解放していないままの状態だったとか…。
あるいは、新規ファイルでも保存に失敗するようであれば、
保存先のパスに対して、書き込み可能なアクセス権限を有していなかった、とか。
> どうすれば正常に戻りますか?
ファイルの読み書き時は、アクセス権の問題やディスク容量の枯渇などによって
何かしらのエラーが発生する可能性があります。
そのためファイルへの保存時には必ず、「Try~Catch ステートメント」を併用して
ファイルアクセスに失敗した場合に、別のファイル名を選択させたり、
あるいは保存を中止するなどの処理を組み込むことが望ましいです。
処理を
投稿者 ギル  (学生)
投稿日時
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+汎用エラーが出てきたんですがどうすれば正常に戻りますか?
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+汎用エラーが出てきたんですがどうすれば正常に戻りますか?
PictureBoxになにかプロパティを設定しているとか、別の処理が介在しているとか、何かありませんか?
新規にWindowsフォームアプリケーションを作成し、Button1, Button2, PictureBox1, PictureBox2, NumericUpDown1 を貼り付けて、プログラムを貼り付けて実行すると同じサイズで表示されるということはないでしょうか。