Форум программистов, компьютерный форум, киберфорум
C# .NET
Войти
Регистрация
Восстановить пароль
Блоги Сообщество Поиск Заказать работу  
 
Рейтинг 4.86/64: Рейтинг темы: голосов - 64, средняя оценка - 4.86
0 / 0 / 0
Регистрация: 25.02.2015
Сообщений: 61

Алгоритм автоматической сборки пазла

06.08.2018, 21:26. Показов 13109. Ответов 7
Метки нет (Все метки)

Студворк — интернет-сервис помощи студентам
Всем привет,
Есть игра пазлы, которая разбирает картинку на блоки и позволяет их перемещать. Существует ли способ создать алгоритм автоматической сборки пазла обратно? Но при условии что этот алгоритм ничего не знает про изначальную картинку, т.е. на вход алгоритма подается только набор картинок и ничего не более. Разве это возможно?
0
IT_Exp
Эксперт
34794 / 4073 / 2104
Регистрация: 17.06.2006
Сообщений: 32,602
Блог
06.08.2018, 21:26
Ответы с готовыми решениями:

Реализация сборки пазла с использованием Drag'N'Drop (чистый JavaScript)
Есть три блока: блок изображения пазла, блок изображения пазла разбитого на 25 частей и такой же блок только полупрозрачный (50%...

Создать алгоритм автоматической нумерации плиток для укладки дорожек
Предприятие занимающееся обустройством пешеходных дорожек тратит много времени на подбор плиток разных размеров для укладки дорожки....

Алгоритм сборки панорамного фото
Здравствуйте. Кто-нибудь программировал сборку фотографий в панорамный снимок. Кто знает хоть какие-нибудь алгоритмы ?

7
Эксперт .NET
 Аватар для Wolfdp
3790 / 1767 / 371
Регистрация: 15.06.2012
Сообщений: 6,543
Записей в блоге: 3
08.08.2018, 06:08

Не по теме:

Цитата Сообщение от alexandro12 Посмотреть сообщение
Разве это возможно?
Нет сынок, это фантастика! (с)



Есть куча вопросов
- игра на шарпе? Т.е. вам нужно как-то выцепить из нее изображения пазлов, для условного черного ящика -- та еще задача.
- пазлы можно соеденить только единым способом? В противном случае вам прийдется писать ИИ, которьій шарит что такое "картинка".
- игра внутри себя знает, какая часть с какой соединяется? Если выудить именно эту инфу, то собрать уже не составит труда. Но это при условии, что валидация "присоединения" не происходит на сервере.
0
187 / 100 / 19
Регистрация: 15.09.2011
Сообщений: 801
09.08.2018, 05:47
alexandro12, предположительно, это возможно. Каждый пазл должен сходиться с другим пазлом по двум условиям:
1) Геометрическое совпадение - пазл выбирается только такой, который может быть физически присоединён с учётом фигуры предыдущего пазла.
2) Каждый пазл содержит в себе часть дробной картинки, когда будет получен вариант подходящих пазлов к пазлу из 1го варианта - можно сравнивать границы. Если границы пазлов по одним координатам совпадают по цвету, то вероятно, эти пазлы рядом. Вообще алгоритм обещает быть сложным. Предположительно будет строиться дерево подходящих пазлов и оценка каждого элемента этого дерева по вероятности - простор для решений просто огромен. Можно выдвигать на олимпиадную задачу)))
0
907 / 664 / 318
Регистрация: 23.10.2016
Сообщений: 1,543
09.08.2018, 12:30
В простейшем случае, когда элементы пазла - прямоугольники одинаковых размеров, можно попробовать воспользоваться жадным алгоритмом. Для оценки, насколько хорошо стыкуются два куска, можно, как предложили выше, сравнивать пикселы на границе этих кусков.

Суть алгоритма: перебором фиксировать каждый кусок в верхнем левом углу картинки. На каждом шаге выбирать такую смежную с уже фиксированными кусками ячейку, для которой наиболее подходящий кусок как можно сильнее отличается по "подходимости" от второго наилучшего куска. Ну и фиксировать в этой ячейке лучший кусок, и так далее, пока все ячейки не станут зафиксированы. Поскольку это был перебор, надо выбрать лучшую из всех получившихся картинок.

Опробовал этот алгоритм на одной картинке, разбив её на 100 кусков. Верный результат получил меньше чем через минуту.
Кликните здесь для просмотра всего текста


Код:
Кликните здесь для просмотра всего текста
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
static class LinqExtensions
{
    public static T GetMaxSource<T, V>(this IEnumerable<T> sequence, Func<T, V> valueSelector)
        where V: IComparable<V>
    {
        if (sequence == null)
        {
            throw new ArgumentNullException(nameof(sequence));
        }
        if (valueSelector == null)
        {
            throw new ArgumentNullException(nameof(valueSelector));
        }
        
        var bestSource = default(T);
        var bestValue = default(V);
        bool gotBest = false;
        
        foreach (var e in sequence)
        {
            if (!gotBest)
            {
                bestSource = e;
                bestValue = valueSelector(e);
                gotBest = true;
                continue;
            }
            
            var newValue = valueSelector(e);
            if (newValue.CompareTo(bestValue) > 0)
            {
                bestValue = newValue;
                bestSource = e;
            }
        }
        
        if (!gotBest)
        {
            throw new ArgumentException(nameof(sequence));
        }
        return bestSource;
    }
}
 
class ThrottledProgress<T> : IProgress<T>
{
    private readonly TimeSpan MinPeriod;
    private TimeSpan LastReport;
    private Stopwatch _stopwatch;
    
    public ThrottledProgress(TimeSpan minPeriod)
    {
        MinPeriod = minPeriod;
    }
    
    public void Report(T value)
    {
        if (_stopwatch == null || _stopwatch.Elapsed - LastReport >= MinPeriod)
        {
            _stopwatch = _stopwatch ?? Stopwatch.StartNew();
            LastReport = _stopwatch.Elapsed;
            ProgressChanged?.Invoke(this, value);
        }
    }
    
    public event EventHandler<T> ProgressChanged;
}
 
class PuzzleProgress : IProgress<Lazy<Color[,]>>
{
    private readonly ThrottledProgress<Lazy<Color[,]>> _throttledProgress;
    private readonly Progress<Color[,]> _userProgress;
    
    public PuzzleProgress(TimeSpan minPeriod)
    {
        _throttledProgress = new ThrottledProgress<Lazy<Color[,]>>(minPeriod);
        _userProgress = new Progress<Color[,]>();
        _throttledProgress.ProgressChanged += (_, progress) =>
        {
            ((IProgress<Color[,]>)_userProgress).Report(progress.Value);
        };
    }
    
    public void Report(Lazy<Color[,]> value)
    {
        _throttledProgress.Report(value);
    }
    
    public event EventHandler<Color[,]> ProgressChanged
    {
        add 
        {
            _userProgress.ProgressChanged += value;
        }
        remove
        {
            _userProgress.ProgressChanged -= value;
        }
    }
}
 
class PuzzleResolver
{
    private readonly int[,] _current;
    private readonly int[,] _suggestion;
    private readonly Color[][,] _slices;
    private readonly Size _sliceSize;
    private readonly Size _sliceCount;
    private double _totalDifference;
    
    private readonly Dictionary<Tuple<int, int>, double> _toRightDifferences = new Dictionary<Tuple<int, int>, double>();
    private readonly Dictionary<Tuple<int, int>, double> _toBottomDifferences = new Dictionary<Tuple<int, int>, double>();
 
    private readonly int[] Dx = { 0, 1, 0, -1 };
    private readonly int[] Dy = { -1, 0, 1, 0 };
 
    public Color[,] Result => ExtractResultFrom(_current);
    
    public PuzzleResolver(IList<Color[,]> slices, Size sourceSize)
    {
        if (slices == null)
        {
            throw new ArgumentNullException(nameof(slices));
        }
        if (sourceSize.Width <= 0 || sourceSize.Height <= 0)
        {
            throw new ArgumentOutOfRangeException(nameof(sourceSize));
        }
        if (slices.Contains(null))
        {
            throw new ArgumentException(nameof(slices));
        }
        _sliceSize = new Size(slices[0].GetLength(0), slices[0].GetLength(1));
        if (slices.Any(slice => slice.GetLength(0) != _sliceSize.Width || slice.GetLength(1) != _sliceSize.Height))
        {
            throw new ArgumentException(nameof(slices));
        }
        if (slices.Count != sourceSize.Width / _sliceSize.Width * sourceSize.Height / _sliceSize.Height)
        {
            throw new InvalidOperationException();
        }
 
        _slices = slices.ToArray();
        _sliceCount = new Size(sourceSize.Width / _sliceSize.Width, sourceSize.Height / _sliceSize.Height);
        _current = new int[sourceSize.Width, sourceSize.Height];
        _suggestion = new int[sourceSize.Width, sourceSize.Height];
 
        int sliceIndex = 0;
        for (int x = 0; x < _sliceCount.Width; x++)
        {
            for (int y = 0; y < _sliceCount.Height; y++)
            {
                _current[x, y] = sliceIndex;
                sliceIndex += 1;
            }
        }
        _totalDifference = GetTotalDifference(_current);
    }
    
    public bool TryImprove()
    {
        for (int width = 1; width >= 1; width--)
        {
            for (int height = 1; height >= 1; height--)
            {
                if (width * height > _sliceCount.Width * + _sliceCount.Height / 2)
                {
                    break;
                }
                
                for (int x = 0; x < _sliceCount.Width - width + 1; x++)
                {
                    for (int y = 0; y < _sliceCount.Height - height + 1; y++)
                    {
                        for (int swapX = x; swapX < _sliceCount.Width - width + 1; swapX++)
                        {
                            for (int swapY = 0; swapY < _sliceCount.Height - height + 1; swapY++)
                            {
                                if (TrySwap(x, y, width, height, swapX, swapY))
                                {
                                    return true;
                                }
                            }
                        }
                    }
                }
            }
        }
        return false;
    }
 
    public Task ProcessAsync(CancellationToken ct = default(CancellationToken), IProgress<Lazy<Color[,]>> progress = null)
    {
        return Task.Run(delegate
        {
            SetupHungry(ct, progress);
            while (TryImprove())
            {
                ct.ThrowIfCancellationRequested();
                progress?.Report(new Lazy<Color[,]>(() => Result));
            }
        });
    }
 
    private void SetupHungry(CancellationToken ct = default(CancellationToken), IProgress<Lazy<Color[,]>> progress = null)
    {
        int middleX = 0;
        int middleY = 0;
 
        for (int middleIndex = 0; middleIndex < _slices.Length; middleIndex++)
        {
            ct.ThrowIfCancellationRequested();
            ResetSuggestion();
            
            var used = new bool[_slices.Length];
            var next = new HashSet<Point>();
            _suggestion[middleX, middleY] = middleIndex;
            used[middleIndex] = true;
            
            for (int k = 0; k < 4; k++)
            {
                int x = middleX + Dx[k];
                int y = middleY + Dy[k];
                if (x >= 0 && x < _sliceCount.Width && y >= 0 && y < _sliceCount.Height)
                {
                    next.Add(new Point(x, y));
                }
            }
            
            while (next.Any())
            {
                var bestReplacement = Tuple.Create(double.MinValue, Point.Empty, 0);
 
                foreach (var cell in next)
                {
                    var bestCandidate = Tuple.Create(double.MaxValue, 0);
                    var secondCandidate = bestCandidate;
                    
                    for (int candidateIndex = 0; candidateIndex < _slices.Length; candidateIndex++)
                    {
                        if (used[candidateIndex])
                        {
                            continue;
                        }
                        double difference = GetCandidateDifference(cell, candidateIndex);
                        if (difference < bestCandidate.Item1)
                        {
                            secondCandidate = bestCandidate;
                            bestCandidate = Tuple.Create(difference, candidateIndex);
                        }
                        else if (difference < secondCandidate.Item1)
                        {
                            secondCandidate = Tuple.Create(difference, candidateIndex);
                        }
                    }
                    
                    if (secondCandidate.Item1 == double.MaxValue || secondCandidate.Item1 - bestCandidate.Item1 > bestReplacement.Item1)
                    {
                        bestReplacement = Tuple.Create(secondCandidate.Item1 - bestCandidate.Item1, cell, bestCandidate.Item2);
                    }
                }
 
                _suggestion[bestReplacement.Item2.X, bestReplacement.Item2.Y] = bestReplacement.Item3;
                used[bestReplacement.Item3] = true;
                next.Remove(bestReplacement.Item2);
                for (int k = 0; k < 4; k++)
                {
                    int x = bestReplacement.Item2.X + Dx[k];
                    int y = bestReplacement.Item2.Y + Dy[k];
                    if (x >= 0 && x < _sliceCount.Width && y >= 0 && y < _sliceCount.Height && _suggestion[x, y] < 0)
                    {
                        next.Add(new Point(x, y));
                    }
                }
            }
            
            var newDifference = GetTotalDifference(_suggestion);
            if (newDifference < _totalDifference)
            {
                _totalDifference = newDifference;
                Array.Copy(_suggestion, _current, _suggestion.Length);
                progress.Report(new Lazy<Color[,]>(() => Result));
            }
        }
    }
 
    private double GetCandidateDifference(Point cell, int candidateIndex)
    {
        int pixelsCompared = 0;
        double totalDifference = 0;
        
        for (int k = 0; k < 4; k++)
        {
            var nextCell = new Point(cell.X + Dx[k], cell.Y + Dy[k]);
            if (nextCell.X >= 0 && nextCell.X < _sliceCount.Width && nextCell.Y >= 0 && nextCell.Y < _sliceCount.Height &&
                _suggestion[nextCell.X, nextCell.Y] >= 0)
            {
                int firstIndex = (cell.X < nextCell.X || cell.Y < nextCell.Y) ? candidateIndex : _suggestion[nextCell.X, nextCell.Y];
                int secondIndex = (firstIndex == candidateIndex) ? _suggestion[nextCell.X, nextCell.Y] : candidateIndex;
                pixelsCompared += (cell.X == nextCell.X) ? _sliceSize.Width : _sliceSize.Height;
                totalDifference += (cell.X == nextCell.X) ? GetDifferenceToBottom(firstIndex, secondIndex) : GetDifferenceToRight(firstIndex, secondIndex);
            }
        }
        
        return (double)totalDifference / pixelsCompared;
    }
 
    private void ResetSuggestion()
    {
        for (int x = 0; x < _sliceCount.Width; x++)
        {
            for (int y = 0; y < _sliceCount.Height; y++)
            {
                _suggestion[x, y] = -1;
            }
        }
    }
 
    private bool TrySwap(int x, int y, int width, int height, int swapX, int swapY)
    {
        var origin = new Rectangle(x, y, width, height);
        var destination = new Rectangle(swapX, swapY, width, height);
        if (origin.IntersectsWith(destination))
        {
            return false;
        }
        
        Array.Copy(_current, _suggestion, _current.Length);
        for (int dx = 0; dx < width; dx++)
        {
            for (int dy = 0; dy < height; dy++)
            {
                Swap(ref _suggestion[x + dx, y + dy], ref _suggestion[swapX + dx, swapY + dy]);
            }
        }
        
        var newDifference = GetTotalDifference(_suggestion);
        if (newDifference < _totalDifference)
        {
            _totalDifference = newDifference;
            Array.Copy(_suggestion, _current, _suggestion.Length);
            return true;
        }
        return false;
    }
    
    private void Swap<T>(ref T x, ref T y)
    {
        var temp = x;
        x = y;
        y = temp;
    }
    
    private double GetTotalDifference(int[,] matrix)
    {
        double total = 0;
        for (int x = 0; x < _sliceCount.Width; x++)
        {
            for (int y = 0; y < _sliceCount.Height; y++)
            {
                if (x < _sliceCount.Width - 1)
                {
                    total += GetDifferenceToRight(matrix[x, y], matrix[x + 1, y]);
                }
                if (y < + _sliceCount.Height - 1)
                {
                    total += GetDifferenceToBottom(matrix[x, y], matrix[x, y + 1]);
                }
            }
        }
        return total;
    }
    
    private double GetDifferenceToBottom(int xSliceIndex, int ySliceIndex)
    {
        double result;
        if (_toBottomDifferences.TryGetValue(Tuple.Create(xSliceIndex, ySliceIndex), out result))
        {
            return result;
        }
        
        var xSlice = _slices[xSliceIndex];
        var ySlice = _slices[ySliceIndex];
        
        result = 0;
        for (int x = 0; x < _sliceSize.Width; x++)
        {
            result += GetDifference(xSlice[x, _sliceSize.Height - 1], ySlice[x, 0]);
        }
        return result;
    }
    
    private double GetDifferenceToRight(int xSliceIndex, int ySliceIndex)
    {
        double result;
        if (_toRightDifferences.TryGetValue(Tuple.Create(xSliceIndex, ySliceIndex), out result))
        {
            return result;
        }
 
        var xSlice = _slices[xSliceIndex];
        var ySlice = _slices[ySliceIndex];
 
        result = 0;
        for (int y = 0; y < _sliceSize.Height; y++)
        {
            result += GetDifference(xSlice[_sliceSize.Width - 1, y], ySlice[0, y]);
        }
        return result;
    }
 
    private double GetDifference(Color x, Color y)
    {
        int dr = Math.Abs(x.R - y.R);
        int dg = Math.Abs(x.G - y.G);
        int db = Math.Abs(x.B - y.B);
        return Math.Sqrt(dr * dr + dg * dg + db * db);
    }
    
    private Color[,] ExtractResultFrom(int[,] suggestion)
    {
        var matrix = new Color[_sliceCount.Width * _sliceSize.Width, _sliceCount.Height * _sliceSize.Height];
        for (int x = 0; x < _sliceCount.Width; x++)
        {
            for (int y = 0; y < _sliceCount.Height; y++)
            {
                if (suggestion[x, y] == -1)
                {
                    continue;
                }
                for (int dx = 0; dx < _sliceSize.Width; dx++)
                {
                    for (int dy = 0; dy < _sliceSize.Height; dy++)
                    {
                        matrix[x * _sliceSize.Width + dx, y * _sliceSize.Height + dy] = _slices[suggestion[x, y]][dx, dy];
                    }
                }
            }
        }
        return matrix;
    }
}
 
static class Slicer
{
    public static IList<Color[,]> Slice(Color[,] source, Size sliceCount)
    {
        if (source == null)
        {
            throw new ArgumentNullException(nameof(source));
        }
        if (sliceCount.Width <= 0 || sliceCount.Height <= 0)
        {
            throw new ArgumentOutOfRangeException(nameof(sliceCount));
        }
        if (source.GetLength(0) % sliceCount.Width != 0 || source.GetLength(1) % sliceCount.Height != 0)
        {
            throw new InvalidOperationException();
        }
        
        var sliceSize = new Size(source.GetLength(0) / sliceCount.Width, source.GetLength(1) / sliceCount.Height);
        var slices = new List<Color[,]>();
        for (int x = 0; x < sliceCount.Width; x++)
        {
            for (int y = 0; y < sliceCount.Height; y++)
            {
                var slice = new Color[source.GetLength(0) / sliceCount.Width, source.GetLength(1) / sliceCount.Height];
                for (int dx = 0; dx < slice.GetLength(0); dx++)
                {
                    for (int dy = 0; dy < slice.GetLength(1); dy++)
                    {
                        slice[dx, dy] = source[x * sliceSize.Width + dx, y * sliceSize.Height + dy];
                    }
                }
                slices.Add(slice);
            }
        }
        return slices;
    }
}
 
static class ImageConverter
{
    public static Color[,] ToMatrix(Image image)
    {
        if (image == null)
        {
            throw new ArgumentNullException(nameof(image));
        }
    
        using (var bitmap = new Bitmap(image))
        {
            var matrix = new Color[bitmap.Width, bitmap.Height];
            for (int x = 0; x < bitmap.Width; x++)
            {
                for (int y = 0; y < bitmap.Height; y++)
                {
                    matrix[x, y] = bitmap.GetPixel(x, y);
                }
            }
            return matrix;
        }
    }
    
    public static Image FromMatrix(Color[,] matrix)
    {
        if (matrix == null)
        {
            throw new ArgumentNullException(nameof(matrix));
        }
        
        var bitmap = new Bitmap(matrix.GetLength(0), matrix.GetLength(1));
        for (int x = 0; x < matrix.GetLength(0); x++)
        {
            for (int y = 0; y < matrix.GetLength(1); y++)
            {
                bitmap.SetPixel(x, y, matrix[x, y]);
            }
        }
        return bitmap;
    }
    
    public static Image Dilate(Image source, Size sizeDivider)
    {
        if (source == null)
        {
            throw new ArgumentNullException(nameof(source));
        }
        if (sizeDivider.Width <= 0 || sizeDivider.Height <= 0)
        {
            throw new ArgumentOutOfRangeException(nameof(sizeDivider));
        }
 
        using (var g = Graphics.FromImage(source))
        {
            var bitmap = new Bitmap(
                source.Width % sizeDivider.Width != 0 ? (source.Width / sizeDivider.Width + 0) * sizeDivider.Width : source.Width,
                source.Height % sizeDivider.Height != 0 ? (source.Height / sizeDivider.Height + 0) * sizeDivider.Height : source.Height, 
                g);
            using (var outputGraphics = Graphics.FromImage(bitmap))
            {
                outputGraphics.DrawImage(source, 0, 0);
            }
            return bitmap;
        }
    }
}
 
class MainForm : Form
{
    #region Controls
 
    private PictureBox _pbxSource;
    private PictureBox _pbxOutput;
    private Button _btnImprove;
    private Button _btnProcess;
    
    private void InitializeComponent()
    {
        base.AutoSize = true;
        
        var layout = new TableLayoutPanel { Parent = this, RowCount = 2, ColumnCount = 3, AutoSize = true };
        layout.GrowStyle = TableLayoutPanelGrowStyle.FixedSize;
        
        _pbxSource = new PictureBox { Parent = layout, Dock = DockStyle.Fill, AutoSize = true, BorderStyle = BorderStyle.FixedSingle };
        _btnImprove = new Button { Parent = layout, Enabled = false, Dock = DockStyle.Top };
        _btnProcess = new Button { Parent = layout, Enabled = false, Dock = DockStyle.Top, Location = new Point(_btnImprove.Left, _btnImprove.Bottom + Padding.All) };
        _pbxOutput = new PictureBox { Parent = layout, Dock = DockStyle.Fill, AutoSize = true, BorderStyle = BorderStyle.FixedSingle };
        
        layout.SetCellPosition(_pbxSource, new TableLayoutPanelCellPosition(0, 0));
        layout.SetCellPosition(_btnImprove, new TableLayoutPanelCellPosition(1, 0));
        layout.SetCellPosition(_btnProcess, new TableLayoutPanelCellPosition(1, 1));
        layout.SetCellPosition(_pbxOutput, new TableLayoutPanelCellPosition(2, 0));
        
        layout.SetRowSpan(_pbxSource, 2);
        layout.SetRowSpan(_pbxOutput, 2);
        
        _btnImprove.Text = "Improve";
        _btnProcess.Text = "Process";
        
        _btnImprove.Click += async delegate
        {
            _btnImprove.Enabled = false;
            _btnProcess.Enabled = false;
            await Task.Run(delegate { _puzzleResolver.TryImprove(); });
            _pbxOutput.Image?.Dispose();
            _pbxOutput.Image = ImageConverter.FromMatrix(_puzzleResolver.Result);
            _btnImprove.Enabled = true;
            _btnProcess.Enabled = true;
        };
        _btnProcess.Click += async delegate
        {
            _btnImprove.Enabled = false;
            _btnProcess.Enabled = false;
 
            var progress = new PuzzleProgress(TimeSpan.FromSeconds(0.5));
            progress.ProgressChanged += async (sender, e) =>
            {
                var lastImage = _pbxOutput.Image;
                _pbxOutput.Image = await Task.Run(delegate { return ImageConverter.FromMatrix(e); });
                lastImage?.Dispose();
            };
            await _puzzleResolver.ProcessAsync(_cts.Token, progress);
            
            _btnImprove.Enabled = true;
            _btnProcess.Enabled = true;
        };
    }
    
    #endregion
    
    private CancellationTokenSource _cts = new CancellationTokenSource();
    private Size _sliceCount = new Size(10, 10);
    private PuzzleResolver _puzzleResolver;
 
    public MainForm()
    {
        InitializeComponent();
        Load += async delegate
        {
            using (var client = new WebClient())
            {
                using (var memory = new MemoryStream())
                {
                    var bytes = await client.DownloadDataTaskAsync(@"https://s00.yaplakal.com/pics/pics_original/6/5/7/11573756.jpg");
                    memory.Write(bytes, 0, bytes.Length);
                    using (var image = Image.FromStream(memory))
                    {
                        _pbxSource.Image = ImageConverter.Dilate(image, _sliceCount);
                    }
                }
            }
 
            var slices = Slicer.Slice(ImageConverter.ToMatrix(_pbxSource.Image), _sliceCount);
            _puzzleResolver = new PuzzleResolver(slices.OrderBy(_ => new Random(Guid.NewGuid().GetHashCode()).Next()).ToList(), _pbxSource.Image.Size);
            _pbxOutput.Image = ImageConverter.FromMatrix(_puzzleResolver.Result);
            _btnImprove.Enabled = true;
            _btnProcess.Enabled = true;
        };
 
        FormClosing += delegate
        {
            _cts.Cancel();  
        };
    }
}
 
void Main()
{
    Application.Run(new MainForm());
}
1
0 / 0 / 0
Регистрация: 24.10.2020
Сообщений: 3
24.10.2020, 22:02
Пытаюсь реализовать данный алгоритм от TopLayer на WPF, однако он срабатывает через раз и не всегда корректно.
Уже второй день не могу понять, где допустил ошибку.

Программа, ничего не зная про алгоритм разрезания исходного изображения, должна собрать его.
Буду очень благодарен за любой совет

C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Imaging;
 
namespace Puzzles
{
 
    struct PixelColor
    {
        public byte Blue;
        public byte Green;
        public byte Red;
        public byte Alpha;
    }
 
    class Alghoritm
    {
        
 
        private List<int> GetNumbers(List<BitmapSource> list)
        {
            List<int> numbers = new List<int>();
 
            for (int i = 1; i <= list.Count; i++)
            {
                if (list.Count % i == 0)
                {
                    numbers.Add(i);
                }
            }
 
            return numbers;
        }
        private PixelColor[,] GetPixels(BitmapSource source)
        {
            if (source.Format != PixelFormats.Bgra32)
                source = new FormatConvertedBitmap(source, PixelFormats.Bgra32, null, 0);
 
            int width = source.PixelWidth;
            int height = source.PixelHeight;
            PixelColor[,] result = new PixelColor[width, height];
 
 
            source.CopyPixels(result, width * 4, 0, true);
 
            return result;
        }
 
        private double GetDifference(PixelColor first, PixelColor second)
        {
            int dr = Math.Abs(first.Red - second.Red);
            int dg = Math.Abs(first.Green - second.Green);
            int db = Math.Abs(first.Blue - second.Blue);
            int da = Math.Abs(first.Alpha - second.Alpha);
 
            return Math.Sqrt(dr * dr + dg * dg + db * db );
        }
 
        private double GetRightDifference(PixelColor[,] left, PixelColor[,] right)
        {
            double rightDifference = 0;
 
            try
            {
                if (left.Length != right.Length)
                    throw new Exception("Щось пішло не так");
 
                for (int i = 0; i < left.GetLength(0); i++)
                {
                    rightDifference += GetDifference(left[i, left.GetLength(1) - 1], right[i, 0]);
                }
 
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
 
            return rightDifference;
 
        }
 
        private double GetBottomDifference(PixelColor[,] up, PixelColor[,] down)
        {
            double bottomDifference = 0;
 
            try
            {
                if (up.Length != down.Length)
                    throw new Exception("Щось пішло не так і тут");
 
                for (int i = 0; i < up.GetLength(1); i++)
                {
                    bottomDifference += GetDifference(up[up.GetLength(0) - 1, i], down[0, i]);
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
 
            return bottomDifference;
        }
 
        private BitmapSource GetRightImage(BitmapSource first, List<BitmapSource> list, ref double totalDifference)
        {
            double min = Int32.MaxValue;
 
            PixelColor[,] left = GetPixels(first);
            BitmapSource next = null;
 
 
            for (int i = 0; i < list.Count; i++)
            {
                PixelColor[,] right = GetPixels(list[i]);
 
                double value = GetRightDifference(left, right);
 
                if (min > value)
                {
                    next = list[i];
                    min = value;
                }
            }
            totalDifference += min;
 
            return next;
        }
 
        private BitmapSource GetBottomImage(BitmapSource first, List<BitmapSource> list, ref double totalDifference)
        {
            double min = Int32.MaxValue;
 
            PixelColor[,] up = GetPixels(first);
            BitmapSource next = null;
 
 
            for (int i = 0; i < list.Count; i++)
            {
                PixelColor[,] down = GetPixels(list[i]);
 
                double value = GetBottomDifference(up, down);
 
                if (min > value)
                {
                    next = list[i];
                    min = value;
                }
            }
 
            totalDifference += min;
 
            return next;
        }
 
        private double GetBestCurrentVariant(List<BitmapSource> list, int row,int col, ref BitmapSource[,] bestChoice)
        {
            BitmapSource bestPiece;
 
            double min = Int32.MaxValue;
 
                for (int j = 0; j < list.Count; j++)    //перебір всіх елементів колекції для даної розстановки
                {
                    double total = 0; //показує повну різницю всього малюнку
 
                    BitmapSource[,] elements = new BitmapSource[row, col];  //отриманий результат
 
                    List<BitmapSource> cash = new List<BitmapSource>(list);     //колекція доступних елементів
 
                    elements[0, 0] = cash[j];
 
                    cash.RemoveAt(j);
 
                    for (int irow = 0; irow < row - 1; irow++)    // перебір по рядкам
                    {
                        for (int icol = 0; icol < col - 1; icol++)  //перебір по стовпцям
                        {
                            //for(int icas = 0; icas<cash.Count;icas++)   //перебір по доступним елементам
                            //{
                            //}
 
                            bestPiece = GetRightImage(elements[irow, icol], cash, ref total);//отримуємо найкращий правий фрагмент
                            elements[irow, icol + 1] = bestPiece;//додаємо отриманий фрагмент до результату
 
                            cash.Remove(bestPiece);//видаляємо недоступний елемент
 
                        }
 
                        bestPiece = GetBottomImage(elements[irow, 0], cash, ref total);//отримуємо найкращий нижній фрагмент
 
                        elements[irow+1, 0] = bestPiece;//додаємо отриманий фрагмент до результату
 
                        cash.Remove(bestPiece);//видаляємо недоступний елемент
                    }
 
                    //додаємо фрагменти до останнього рядка
                    for (int icol = 0; icol < col - 1; icol++)  //перебір по стовпцям
                    {
                        bestPiece = GetRightImage(elements[row - 1, icol], cash, ref total);//отримуємо найкращий правий фрагмент
                        elements[row - 1, icol + 1] = bestPiece;//додаємо отриманий фрагмент до результату
 
                        cash.Remove(bestPiece);//видаляємо недоступний елемент
                    }
 
                    if (min > total)
                    {
                        bestChoice = (BitmapSource[,])elements.Clone();
                        min = total;
                    }
                }
 
            return min;
        }
 
        public BitmapSource[,] GetBestPuzzleImage(List<BitmapImage> list)
        {
            List<BitmapSource> bmp = new List<BitmapSource>();
 
            for(int i=0;i<list.Count;i++)
            {
                BitmapSource temp = (BitmapSource)list[i];
                bmp.Add(temp);
            }
 
            List<int> numbers = new List<int>(GetNumbers(bmp));
 
            BitmapSource[,] bestChoice = null;
 
            double min = Int32.MaxValue;  //значення найменшої різниці малюнку(для ивзанчення найкращого результату)
 
            for (int i = 0; i < numbers.Count;i++)
            {
                int row = numbers[i];   //кількість рядків
                int col = list.Count / row;     //кількість стовпців
 
                BitmapSource[,] possibleChoice = new BitmapSource[row,col];
 
                double value = GetBestCurrentVariant(bmp, row, col, ref possibleChoice);
 
                if (min > value)
                {
                    bestChoice = (BitmapSource[,])possibleChoice.Clone();
 
                    min = value;
                }
            }
 
            return bestChoice;
        }
    }
}
0
1152 / 860 / 263
Регистрация: 30.04.2009
Сообщений: 3,603
25.10.2020, 07:50
Dragon4ik, если "через раз" означает на разных картинках то ничего удивительного.
На стыках не должно быть "разрывов" - резких изменений цвета. Чем больше таких точек тем больше вероятность что картинка соберется не так как ожидается.
0
0 / 0 / 0
Регистрация: 24.10.2020
Сообщений: 3
25.10.2020, 11:35
nicolas2008, Смисл в том что, работает оно через раз с одним изображением в зависимости от количества фрагментов. И я не понимаю, как тогда разрезать изображение, чтоби между фрагментами не било резкого разрива пикселей(я просто использовал CroppedBitmap и указивал область, которую нужно вирезать)
0
1152 / 860 / 263
Регистрация: 30.04.2009
Сообщений: 3,603
25.10.2020, 14:51
Dragon4ik, сам этот алгоритм основан на поиске похожести цветов на стыках. Наверное можно как то его подтюнить, усреднять цвета из нескольких крайних рядов например, но 100% результата все равно не будет.
Представь себе шахматную доску разрезанную ровно по клеткам, или абстрактрую картину с хаотичными ломаными линиями - тут оригинал не может быть восстановлен в принципе, даже человеком.
1
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
BasicMan
Эксперт
29316 / 5623 / 2384
Регистрация: 17.02.2009
Сообщений: 30,364
Блог
25.10.2020, 14:51
Помогаю со студенческими работами здесь

Кубик Рубика алгоритм сборки
Добрый вечер форумчане нужно реализовать сборку кубика рубика у меня щас только идея с вербального языка перевести на компьютерный язык но...

Алгоритм сборки пирамидки Мефферта
Нужно реализовать алгоритм сборки пирамиды Мефферта.Очень похоже на Кубик-Рубика,но как начинаешь делать,то все абсолютно другое. Помогите...

Меню из пазла
Всем доброе утро! У меня квадрат из пазл 4 картинки, хочу соединить на главной странице сайта, собрать так сказать в пазл и каждая...

Сборка пазла в Android
здравствуйте, хочу собрать картинку (как мозаику) из частей, нужно элементы расставить на свои места в соответствии с номером. примерно как...

Хочу собрать алгоритм сборки кубика-рубика по этой книжке
Хочу собрать алгоритм сборки кубика-рубика по этой книжке. http://old-dos.ru/books/9/5/5/%20%EF%EE%E8%F1%EA%E0.pdf


Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
8
Ответ Создать тему
Новые блоги и статьи
Контроль заполнения и очистка дат в зависимости от значения перечислений
Maks 12.04.2026
Алгоритм из решения ниже реализован на примере нетипового документа "ПланированиеПерсонала", разработанного в конфигурации КА2. Задача: реализовать контроль корректности заполнения дат назначения. . .
Архитектура слоя интернета для сервера-слоя.
Hrethgir 11.04.2026
В продолжение https:/ / www. cyberforum. ru/ blogs/ 223907/ 10860. html Знаешь что я подумал? Раз мы все источники пишем в голове ветки, то ничего не мешает добавить в голову такой источник, который сам. . .
Подстановка значения реквизита справочника в табличную часть документа
Maks 10.04.2026
Алгоритм из решения ниже реализован на примере нетипового документа "ПланированиеПерсонала", разработанного в конфигурации КА2. Задача: при выборе сотрудника (справочник Сотрудники) в ТЧ документа. . .
Очистка реквизитов документа при копировании
Maks 09.04.2026
Алгоритм из решения ниже применим как для типовых, так и для нетиповых документов на самых различных конфигурациях. Задача: при копировании документа очищать определенные реквизиты и табличную. . .
модель ЗдравоСохранения 8. Подготовка к разному выполнению заданий
anaschu 08.04.2026
https:/ / github. com/ shumilovas/ med2. git main ветка * содержимое блока дэлэй из старой модели теперь внутри зайца новой модели 8ATzM_2aurI
Блокировка документа от изменений, если он открыт у другого пользователя
Maks 08.04.2026
Алгоритм из решения ниже реализован на примере нетипового документа, разработанного в конфигурации КА2. Задача: запретить редактирование документа, если он открыт у другого пользователя. / / . . .
Система безопасности+живучести для сервера-слоя интернета (сети). Двойная привязка.
Hrethgir 08.04.2026
Далее были размышления о системе безопасности. Сообщения с наклонным текстом - мои. А как нам будет можно проверить, что ссылка наша, а не подделана хулиганами, которая выбросит на другую ветку и. . .
Модель ЗдрввоСохранения 7: больше работников, больше ресурсов.
anaschu 08.04.2026
работников и заданий может быть сколько угодно, но настроено всё так, что используется пока что только 20% kYBz3eJf3jQ
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2026, CyberForum.ru