Переставьте пиксели в изображении, чтобы оно не могло быть распознано, а затем верните его обратно


86

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

Вы можете написать две функции - для кодирования и декодирования, однако одна функция, которая применяется неоднократно, дает оригинальное изображение (пример в математике - f(x) = 1 - x) является бонусом.

Кроме того, создание некоторого шаблона в выводе также дает бонус.

Изображение может быть представлено в виде 1D / 2D-массива или объекта изображения, если ваш язык поддерживает это. Обратите внимание, что вы можете изменить только порядок пикселей!

Будет логично выбрать в качестве кода победителя, который производит менее узнаваемое изображение, однако я не знаю, как его точно измерить, все способы, которые я могу себе представить, могут быть обмануты. Поэтому я выбрал этот вопрос как конкурс на популярность - пусть пользователи выбирают лучший ответ!

Тестовое изображение 1 (800 x 422 px): Тестовое изображение 2 (800 x 480 px): Пожалуйста, предоставьте выходное изображение кода.


Вопрос очень многословный: «Напишите алгоритм шифрования для изображений, результатом которых является изображение».
Дэвид Ричерби

3
@DavidRicherby ... который использует те же пиксели / цвета. Пять черных пикселей в «обычном изображении» -> пять черных пикселей в «зашифрованном изображении».
Даниэль Бек

2
@ user2992539 Хорошо, в этом случае вы можете явно указать, что это используется в качестве тай-брейка. В противном случае просто сказать, что это бонус, не очень важно.
Мартин Эндер

3
Этот вопрос заставил меня задуматься о карте кошек Арнольда . Я не думаю, что он вполне подходит для этой цели, но он интересен точно так же - повторение карты достаточное количество раз возвращает вас к исходному изображению.
Трихоплакс

4
Теперь в другом месте в сети Stack Exchange: Security.SE всех мест
дверная ручка

Ответы:


58

Python 2.7 (с PIL) - нет псевдослучайности

Я разбиваю изображение на 2 на 2 блока (игнорируя остаток) и поворачиваю каждый блок на 180 градусов, затем делаю то же самое с 3 на 3 блоками, затем 4 и т. Д. До некоторого параметра BLKSZ. Затем я делаю то же самое для BLKSZ-1, затем BLKSZ-2, вплоть до 3, а затем 2. Этот метод полностью меняет себя; функция расшифровки - это функция шифрования.

Код :

from PIL import Image
import math

im = Image.open("ST1.png", "r")
arr = im.load() #pixel data stored in this 2D array

def rot(A, n, x1, y1): #this is the function which rotates a given block
    temple = []
    for i in range(n):
        temple.append([])
        for j in range(n):
            temple[i].append(arr[x1+i, y1+j])
    for i in range(n):
        for j in range(n):
            arr[x1+i,y1+j] = temple[n-1-i][n-1-j]


xres = 800
yres = 480
BLKSZ = 50 #blocksize
for i in range(2, BLKSZ+1):
    for j in range(int(math.floor(float(xres)/float(i)))):
        for k in range(int(math.floor(float(yres)/float(i)))):
            rot(arr, i, j*i, k*i)
for i in range(3, BLKSZ+1):
    for j in range(int(math.floor(float(xres)/float(BLKSZ+2-i)))):
        for k in range(int(math.floor(float(yres)/float(BLKSZ+2-i)))):
            rot(arr, BLKSZ+2-i, j*(BLKSZ+2-i), k*(BLKSZ+2-i))

im.save("ST1OUT "+str(BLKSZ)+".png")

print("Done!")

В зависимости от размера блока, вы можете сделать так, чтобы вычисления уничтожали все сходство с исходным изображением: (BLKSZ = 50) введите описание изображения здесь введите описание изображения здесь

Или сделайте вычисления эффективными: (BLKSZ = 10) введите описание изображения здесь введите описание изображения здесь


6
Похоже, лучшие результаты будут, если BLKSZ будет составлять примерно половину размера изображения. В любом случае, мне нравится алгоритм, и для маленьких BLKSZ это выглядит как современное искусство! Здорово!
Сомниум

11
Я всегда приветствую питона.
qwr

Вместо скремблирования для всех значений от 2 до 50, может быть, вам следует использовать только простые числа?
Нил

@Neil Вероятно, тогда это будет выглядеть более случайным и менее художественным.
Сомниум

BLKSZ = 10Пейзаж это действительно круто!
wchargin

52

C #, Winform

Редактировать Изменяя способ заполнения массива координат, вы можете иметь разные шаблоны - см. Ниже.

Вам нравится этот тип картины?

Пейзаж

Абстрактные

Бонус:

Крик Scream Scrambled

Произвольная замена ровно один раз всех пикселей в верхней половине со всеми пикселями в нижней половине. Повторите ту же процедуру для расшифровки (бонус).

Код

Scramble.cs

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Windows.Forms;
using System.Drawing.Imaging;
using System.IO;

namespace Palette
{
    public partial class Scramble : Form
    {
        public Scramble()
        {
            InitializeComponent();
        }

        public struct Coord
        {
            public int x, y;
        }

        private void Work(Bitmap srcb, Bitmap outb)
        {
            int w = srcb.Width, h = srcb.Height;
            Coord[] coord = new Coord[w * h];

            FastBitmap fsb = new FastBitmap(srcb);
            FastBitmap fob = new FastBitmap(outb);
            fsb.LockImage();
            fob.LockImage();
            ulong seed = 0;
            int numpix = 0;
            for (int y = 0; y < h; y++)
                for (int x = 0; x < w; numpix++, x++)
                {
                    coord[numpix].x = x;
                    coord[numpix].y = y;
                    uint color = fsb.GetPixel(x, y);
                    seed += color;
                    fob.SetPixel(x, y, color);
                }
            fsb.UnlockImage();
            fob.UnlockImage();
            pbOutput.Refresh();
            Application.DoEvents();

            int half = numpix / 2;
            int limit = half;
            XorShift rng = new XorShift(seed);
            progressBar.Visible = true;
            progressBar.Maximum = limit;

            fob.LockImage();
            while (limit > 0)
            {
                int p = (int)(rng.next() % (uint)limit);
                int q = (int)(rng.next() % (uint)limit);
                uint color = fob.GetPixel(coord[p].x, coord[p].y); 
                fob.SetPixel(coord[p].x, coord[p].y, fob.GetPixel(coord[half+q].x, coord[half+q].y)); 
                fob.SetPixel(coord[half+q].x, coord[half+q].y, color); 
                limit--;
                if (p < limit)
                {
                    coord[p]=coord[limit];
                }
                if (q < limit)
                {
                    coord[half+q]=coord[half+limit];
                }
                if ((limit & 0xfff) == 0)
                {
                    progressBar.Value = limit;
                    fob.UnlockImage();
                    pbOutput.Refresh();
                    fob.LockImage();
                }
            }
            fob.UnlockImage();
            pbOutput.Refresh();
            progressBar.Visible = false; 
        }

        void DupImage(PictureBox s, PictureBox d)
        {
            if (d.Image != null)
                d.Image.Dispose();
            d.Image = new Bitmap(s.Image.Width, s.Image.Height);  
        }

        void GetImagePB(PictureBox pb, string file)
        {
            Bitmap bms = new Bitmap(file, false);
            Bitmap bmp = bms.Clone(new Rectangle(0, 0, bms.Width, bms.Height), PixelFormat.Format32bppArgb);
            bms.Dispose(); 
            if (pb.Image != null)
                pb.Image.Dispose();
            pb.Image = bmp;
        }

        private void btnOpen_Click(object sender, EventArgs e)
        {
            OpenFileDialog openFileDialog = new OpenFileDialog();

            openFileDialog.InitialDirectory = "c:\\temp\\";
            openFileDialog.Filter = "Image Files(*.BMP;*.JPG;*.PNG)|*.BMP;*.JPG;*.PNG|All files (*.*)|*.*";
            openFileDialog.FilterIndex = 1;
            openFileDialog.RestoreDirectory = true;

            if (openFileDialog.ShowDialog() == DialogResult.OK)
            {
                try
                {
                    string file = openFileDialog.FileName;
                    GetImagePB(pbInput, file);
                    pbInput.Tag = file;
                    DupImage(pbInput, pbOutput);
                    Work(pbInput.Image as Bitmap, pbOutput.Image as Bitmap);
                    file = Path.GetDirectoryName(file) + Path.DirectorySeparatorChar + Path.GetFileNameWithoutExtension(file) + ".scr.png";
                    pbOutput.Image.Save(file);
                }
                catch (Exception ex)
                {
                    MessageBox.Show("Error: Could not read file from disk. Original error: " + ex.Message);
                }

            }
        }
    }

    //Adapted from Visual C# Kicks - http://www.vcskicks.com/
    unsafe public class FastBitmap
    {
        private Bitmap workingBitmap = null;
        private int width = 0;
        private BitmapData bitmapData = null;
        private Byte* pBase = null;

        public FastBitmap(Bitmap inputBitmap)
        {
            workingBitmap = inputBitmap;
        }

        public BitmapData LockImage()
        {
            Rectangle bounds = new Rectangle(Point.Empty, workingBitmap.Size);

            width = (int)(bounds.Width * 4 + 3) & ~3;

            //Lock Image
            bitmapData = workingBitmap.LockBits(bounds, ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);
            pBase = (Byte*)bitmapData.Scan0.ToPointer();
            return bitmapData;
        }

        private uint* pixelData = null;

        public uint GetPixel(int x, int y)
        {
            pixelData = (uint*)(pBase + y * width + x * 4);
            return *pixelData;
        }

        public uint GetNextPixel()
        {
            return *++pixelData;
        }

        public void GetPixelArray(int x, int y, uint[] Values, int offset, int count)
        {
            pixelData = (uint*)(pBase + y * width + x * 4);
            while (count-- > 0)
            {
                Values[offset++] = *pixelData++;
            }
        }

        public void SetPixel(int x, int y, uint color)
        {
            pixelData = (uint*)(pBase + y * width + x * 4);
            *pixelData = color;
        }

        public void SetNextPixel(uint color)
        {
            *++pixelData = color;
        }

        public void UnlockImage()
        {
            workingBitmap.UnlockBits(bitmapData);
            bitmapData = null;
            pBase = null;
        }
    }

    public class XorShift
    {
        private ulong x; /* The state must be seeded with a nonzero value. */

        public XorShift(ulong seed)
        {
            x = seed;
        }

        public ulong next()
        {
            x ^= x >> 12; // a
            x ^= x << 25; // b
            x ^= x >> 27; // c
            return x * 2685821657736338717L;
        }
    }
} 

Scramble.designer.cs

namespace Palette
{
    partial class Scramble
    {
        private System.ComponentModel.IContainer components = null;

        protected override void Dispose(bool disposing)
        {
            if (disposing && (components != null))
            {
                components.Dispose();
            }
            base.Dispose(disposing);
        }

        private void InitializeComponent()
        {
            this.panel = new System.Windows.Forms.FlowLayoutPanel();
            this.pbInput = new System.Windows.Forms.PictureBox();
            this.pbOutput = new System.Windows.Forms.PictureBox();
            this.progressBar = new System.Windows.Forms.ProgressBar();
            this.btnOpen = new System.Windows.Forms.Button();
            this.panel.SuspendLayout();
            ((System.ComponentModel.ISupportInitialize)(this.pbInput)).BeginInit();
            ((System.ComponentModel.ISupportInitialize)(this.pbOutput)).BeginInit();
            this.SuspendLayout();
            // 
            // panel
            // 
            this.panel.AutoScroll = true;
            this.panel.AutoSize = true;
            this.panel.Controls.Add(this.pbInput);
            this.panel.Controls.Add(this.pbOutput);
            this.panel.Dock = System.Windows.Forms.DockStyle.Top;
            this.panel.Location = new System.Drawing.Point(0, 0);
            this.panel.Name = "panel";
            this.panel.Size = new System.Drawing.Size(748, 306);
            this.panel.TabIndex = 3;
            // 
            // pbInput
            // 
            this.pbInput.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
            this.pbInput.Location = new System.Drawing.Point(3, 3);
            this.pbInput.MinimumSize = new System.Drawing.Size(100, 100);
            this.pbInput.Name = "pbInput";
            this.pbInput.Size = new System.Drawing.Size(100, 300);
            this.pbInput.SizeMode = System.Windows.Forms.PictureBoxSizeMode.AutoSize;
            this.pbInput.TabIndex = 3;
            this.pbInput.TabStop = false;
            // 
            // pbOutput
            // 
            this.pbOutput.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
            this.pbOutput.Location = new System.Drawing.Point(109, 3);
            this.pbOutput.MinimumSize = new System.Drawing.Size(100, 100);
            this.pbOutput.Name = "pbOutput";
            this.pbOutput.Size = new System.Drawing.Size(100, 300);
            this.pbOutput.SizeMode = System.Windows.Forms.PictureBoxSizeMode.AutoSize;
            this.pbOutput.TabIndex = 4;
            this.pbOutput.TabStop = false;
            // 
            // progressBar
            // 
            this.progressBar.Dock = System.Windows.Forms.DockStyle.Bottom;
            this.progressBar.Location = new System.Drawing.Point(0, 465);
            this.progressBar.Name = "progressBar";
            this.progressBar.Size = new System.Drawing.Size(748, 16);
            this.progressBar.TabIndex = 5;
            // 
            // btnOpen
            // 
            this.btnOpen.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
            this.btnOpen.Location = new System.Drawing.Point(12, 429);
            this.btnOpen.Name = "btnOpen";
            this.btnOpen.Size = new System.Drawing.Size(53, 30);
            this.btnOpen.TabIndex = 6;
            this.btnOpen.Text = "Start";
            this.btnOpen.UseVisualStyleBackColor = true;
            this.btnOpen.Click += new System.EventHandler(this.btnOpen_Click);
            // 
            // Scramble
            // 
            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
            this.BackColor = System.Drawing.SystemColors.ControlDark;
            this.ClientSize = new System.Drawing.Size(748, 481);
            this.Controls.Add(this.btnOpen);
            this.Controls.Add(this.progressBar);
            this.Controls.Add(this.panel);
            this.Name = "Scramble";
            this.Text = "Form1";
            this.panel.ResumeLayout(false);
            this.panel.PerformLayout();
            ((System.ComponentModel.ISupportInitialize)(this.pbInput)).EndInit();
            ((System.ComponentModel.ISupportInitialize)(this.pbOutput)).EndInit();
            this.ResumeLayout(false);
            this.PerformLayout();

        }


        private System.Windows.Forms.FlowLayoutPanel panel;
        private System.Windows.Forms.PictureBox pbOutput;
        private System.Windows.Forms.ProgressBar progressBar;
        private System.Windows.Forms.PictureBox pbInput;
        private System.Windows.Forms.Button btnOpen;
    }
}

Program.cs

using System;
using System.Collections.Generic;
using System.Windows.Forms;

namespace Palette
{
  static class Program
  {
    [STAThread]
    static void Main()
    {
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);
        Application.Run(new Scramble());
    }
  }
}

Установите флажок «Небезопасный код» в свойстве проекта для компиляции.

Сложная картина

свалка

Измените первую часть рабочей функции, вплоть до Application.DoEvents:

        int w = srcb.Width, h = srcb.Height;
        string Msg = "Scramble";

        Graphics gr = Graphics.FromImage(outb);

        Font f = new Font("Arial", 100, FontStyle.Bold);
        var size = gr.MeasureString(Msg, f);
        f = new Font("Arial", w / size.Width * 110, FontStyle.Bold);
        size = gr.MeasureString(Msg, f);
        gr.DrawString(Msg, f, new SolidBrush(Color.White), (w - size.Width) / 2, (h - size.Height) / 2);

        gr.Dispose();

        Coord[] coord = new Coord[w * h];
        FastBitmap fsb = new FastBitmap(srcb);
        FastBitmap fob = new FastBitmap(outb);
        fsb.LockImage();
        fob.LockImage();
        ulong seed = 1;
        int numpix = h * w;
        int c1 = 0, c2 = numpix;
        int y2 = h / 2;

        int p2 = numpix/2;

        for (int p = 0; p < p2; p++)
        {
            for (int s = 1; s > -2; s -= 2)
            {
                int y = (p2+s*p) / w;
                int x = (p2+s*p) % w;

                uint d = fob.GetPixel(x, y);
                if (d != 0)
                {
                    c2--;
                    coord[c2].x = x;
                    coord[c2].y = y;
                }
                else
                {
                    coord[c1].x = x;
                    coord[c1].y = y;
                    c1++;
                }
                fob.SetPixel(x, y, fsb.GetPixel(x, y));
            }
        }
        fsb.UnlockImage();
        fob.UnlockImage();
        pbOutput.Refresh();
        Application.DoEvents();

1
Интересно) Мне интересно, возможно ли при подобном подходе сделать более сложные шаблоны в выводе.
Сомниум

1
Хорошая идея - что происходит со средней линией при нечетном количестве строк?
flawr

1
@ Flawr разделение на пиксель. Если есть нечетное количество пикселей, последний остается нетронутым. Если число строк нечетное, левая половина среднего ряда - это «верхняя сторона», а правая половина - «нижняя сторона».
edc65

1
@ user2992539 Я думаю, что вы можете подразделить больше - даже шахматная доска. С большим количеством подразделений изображение становится более узнаваемым.
edc65

7
Понравилась ваша версия "Scramble"!)
Сомниум

43

С, произвольное размытие, легко обратимый

Поздно на вечеринку. Вот моя запись!

Этот метод делает скремблирующее размытие. Я называю это схваткой . Это очень просто. В цикле он выбирает случайный пиксель и затем меняет его на случайно выбранный соседний пиксель в модели тороидального холста. Вы указываете максимальное расстояние, определяющее, что означает «соседний пиксель» (1 означает, что всегда выбирается соседний пиксель), количество итераций и, необязательно, начальное число случайных чисел. Чем больше максимальное расстояние и чем больше число итераций, тем более размытым будет результат.

Это обратимо, если указать отрицательное число итераций (это просто удобство интерфейса командной строки; фактически нет такой вещи, как отрицательные итерации). Внутренне он использует собственный 64-разрядный LCPRNG (генератор линейных конгруэнтных псевдослучайных чисел) и предварительно генерирует блок значений. Таблица позволяет циклически проходить через блок вперед или назад для скремблирования или дешифрования соответственно.

демонстрация

Для первых двух изображений при прокрутке вниз каждое изображение размыто с использованием более высокого максимального смещения: самым верхним является исходное изображение (например, смещение на 0 пикселей), за которым следуют 1, 2, 4, 8, 16, 32, 64 , 128 и, наконец, 256. Число итераций составляет 10⁶ = 1 000 000 для всех изображений ниже.

Для вторых двух изображений каждое изображение размыто с использованием постепенно уменьшающегося смещения - например, от наиболее размытого до наименее размытого - от максимального смещения от 256 до 0. Наслаждайтесь!

Пейзаж Абстрактные

А для следующих двух изображений вы можете увидеть прогрессии в полном размере здесь и здесь :

Во все тяжкие Симпсоны

Код

Я взломал это вместе примерно через час, когда проснулся этим утром, и в нем почти нет документации. Я мог бы вернуться через несколько дней и добавить дополнительную документацию позже, если люди об этом попросят.

//=============================================================================
// SCRAMBLUR
//
// This program is a image-processing competition entry which scrambles or
// descrambles an image based on a pseudorandom process.  For more details,
// information, see:
//
//    http://codegolf.stackexchange.com/questions/35005
//
// It is assumed that you have the NETPBM package of image-processing tools
// installed on your system.  This can be obtained from:
//
//    http://netpbm.sourceforge.net/
//
// or by using your system's package manager, e.g., yum, apt-get, port, etc.
//
// Input to the program is a 24-bit PNM image (type "P6").  Output is same.
// Example command-line invocation:
//
// pngtopnm original.png  | scramblur 100  1000000 | pnmtopng >scrambled.png
// pngtopnm scrambled.png | scramblur 100 -1000000 | pnmtopng >recovered.png
//
//
// Todd S. Lehman, July 2014

#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <string.h>

typedef uint8_t uint8;
typedef uint64_t uint64;

//-----------------------------------------------------------------------------
// PIXEL STRUCTURE

#pragma pack(push, 1)
typedef struct
{
  uint8 r, g, b;     // Red, green, and blue color components
}
Pixel;
#pragma pack(pop)

//-----------------------------------------------------------------------------
// IMAGE STRUCTURE

typedef struct
{
  int width;          // Width of image in pixels
  int height;         // Height of image in pixels
  int pixel_count;    // Total number of pixels in image (e.g., width * height)
  int maxval;         // Maximum pixel component value (e.g., 255)
  Pixel *data;        // One-dimensional array of pixels
}
Image;

//-----------------------------------------------------------------------------
// 64-BIT LCG TABLE

static const long lcg64_table_length = 1000000;  // 10⁶ entries => 8 Megabytes

static uint64 lcg64_table[lcg64_table_length];

//-----------------------------------------------------------------------------
// GET 64-BIT LCG VALUE FROM TABLE

uint64 lcg64_get(long const iteration)
{
  return lcg64_table[iteration % lcg64_table_length];
}

//-----------------------------------------------------------------------------
// INITIALIZE 64-BIT LCG TABLE

void lcg64_init(uint64 const seed)
{
  uint64 x = seed;
  for (long iteration = 0; iteration < lcg64_table_length; iteration++)
  {
    uint64 const a = UINT64_C(6364136223846793005);
    uint64 const c = UINT64_C(1442695040888963407);
    x = (x * a) + c;
    lcg64_table[iteration] = x;
  }
}

//-----------------------------------------------------------------------------
// READ BINARY PNM IMAGE

Image image_read(FILE *const file)
{
  Image image = { .data = NULL };

  char *line = NULL;
  size_t linecap = 0;

  // Read image type.  (Currently only P6 is supported here.)
  if (getline(&line, &linecap, file) < 0) goto failure;
  if (strcmp(line, "P6\n") != 0) goto failure;

  // Read width and height of image in pixels.
  {
    if (getline(&line, &linecap, file) < 0) goto failure;
    char *pwidth = &line[0];
    char *pheight = strchr(line, ' ');
    if (pheight != NULL) pheight++; else goto failure;
    image.width = atoi(pwidth);
    image.height = atoi(pheight);
    image.pixel_count = image.width * image.height;
  }

  // Read maximum color value.  (Currently only 255 is supported here.)
  {
    if (getline(&line, &linecap, file) < 0) goto failure;
    image.maxval = atoi(line);
    if (image.maxval != 255)
      goto failure;
  }

  // Allocate image buffer and read image data.
  if (!(image.data = calloc(image.pixel_count, sizeof(Pixel))))
    goto failure;

  if (fread(image.data, sizeof(Pixel), image.pixel_count, file) !=
      image.pixel_count)
    goto failure;

success:
  free(line);
  return image;

failure:
  free(line);
  free(image.data); image.data = NULL;
  return image;
}

//-----------------------------------------------------------------------------
// WRITE BINARY PNM IMAGE

void image_write(const Image image, FILE *const file)
{
  printf("P6\n");
  printf("%d %d\n", image.width, image.height);
  printf("%d\n", image.maxval);
  (void)fwrite(image.data, sizeof(Pixel), image.pixel_count, file);
}

//-----------------------------------------------------------------------------
// DISCARD IMAGE

void image_discard(Image image)
{
  free(image.data);
}

//-----------------------------------------------------------------------------
// SCRAMBLE OR UNSCRAMBLE IMAGE

void image_scramble(Image image,
                    int const max_delta,
                    long const iterations,
                    uint64 const lcg64_seed)
{
  if (max_delta == 0) return;

  int neighborhood1 = (2 * max_delta) + 1;
  int neighborhood2 = neighborhood1 * neighborhood1;

  lcg64_init(lcg64_seed);

  long iteration_start = (iterations >= 0)? 0 : -iterations;
  long iteration_end   = (iterations >= 0)? iterations : 0;
  long iteration_inc   = (iterations >= 0)? 1 : -1;

  for (long iteration = iteration_start;
       iteration != iteration_end;
       iteration += iteration_inc)
  {
    uint64 lcg64 = lcg64_get(iteration);

    // Choose random pixel.
    int pixel_index = (int)((lcg64 >> 0) % image.pixel_count);

    // Choose random pixel in the neighborhood.
    int d2 = (int)((lcg64 >> 8) % neighborhood2);
    int dx = (d2 % neighborhood1) - (neighborhood1 / 2);
    int dy = (d2 / neighborhood1) - (neighborhood1 / 2);
    int other_pixel_index = pixel_index + dx + (dy * image.width);
    while (other_pixel_index < 0)
      other_pixel_index += image.pixel_count;
    other_pixel_index %= image.pixel_count;

    // Swap pixels.
    Pixel t = image.data[pixel_index];
    image.data[pixel_index] = image.data[other_pixel_index];
    image.data[other_pixel_index] = t;
  }
}

//-----------------------------------------------------------------------------
int main(const int argc, char const *const argv[])
{
  int max_delta     = (argc > 1)? atoi(argv[1]) : 1;
  long iterations   = (argc > 2)? atol(argv[2]) : 1000000;
  uint64 lcg64_seed = (argc > 3)? (uint64)strtoull(argv[3], NULL, 10) : 0;

  Image image = image_read(stdin);
  if (!image.data) { fprintf(stderr, "Invalid input\n"), exit(1); }

  image_scramble(image, max_delta, iterations, lcg64_seed);

  image_write(image, stdout);

  image_discard(image);

  return 0;
}

4
Пролистал мимо этого ответа, выглядит потрясающе
Томас

1
Этот ответ действительно высокий. Как вы думаете, вы могли бы переместить дополнительные изображения (т.е. все, кроме двух тестовых изображений, полностью размытых) в стороннюю галерею?
Тим С.

@TimS. - сделанный! сжать их до крошечных миниатюр.
Тодд Леман

42

Python 3.4

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

При достижении бонуса 2 при использовании дополнительного ключевого изображения бонус 1 не теряется. Программа по-прежнему самообращена при условии, что она снова запускается с тем же ключевым изображением.

Стандартное использование

Тестовое изображение 1:

Скремблированное тестовое изображение 1

Тестовое изображение 2:

Скремблированное тестовое изображение 2

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

Процесс скремблирования является самообращенным, потому что список всех пикселей разбит на 2 цикла, так что каждый пиксель заменяется одним и только одним другим пикселем. Второй запуск приводит к замене каждого пикселя на тот, с которым он был вначале заменен, и все возвращается к тому, как это началось. Если есть нечетное количество пикселей, будет один, который не перемещается.

Благодаря ответу mfvonh как первому предложить 2 цикла.

Использование с ключевым изображением

Скремблирующее тестовое изображение 1 с тестовым изображением 2 в качестве ключевого изображения

Схватка тест 1 с тестом 2

Скремблирующее тестовое изображение 2 с тестовым изображением 1 в качестве ключевого изображения

Схватка тест 2 с тестом 1

Запуск программы со вторым аргументом файла изображения (изображение ключа) делит исходное изображение на области на основе изображения ключа. Каждая из этих областей разделена на 2 цикла по отдельности, так что все скремблирование происходит внутри областей, и пиксели не перемещаются из одной области в другую. Это распределяет пиксели по каждой области, и таким образом области становятся однородным крапчатым цветом, но с немного другим средним цветом для каждой области. Это дает очень грубое приближение к ключевому изображению в неправильных цветах.

При повторном запуске меняются одинаковые пары пикселей в каждой области, поэтому каждая область восстанавливается в исходное состояние, и изображение в целом снова появляется.

Благодаря EDC65 ответ как первый предложил разделить изображение на регионы. Я хотел бы расширить это, чтобы использовать произвольные регионы, но подход замены всего в области 1 на все в области 2 означал, что регионы должны быть одинакового размера. Мое решение состоит в том, чтобы сохранить регионы изолированными друг от друга и просто перетасовать каждый регион в себя. Поскольку области больше не должны быть одинакового размера, становится проще применять области произвольной формы.

Код

import os.path
from PIL import Image   # Uses Pillow, a fork of PIL for Python 3
from random import randrange, seed


def scramble(input_image_filename, key_image_filename=None,
             number_of_regions=16777216):
    input_image_path = os.path.abspath(input_image_filename)
    input_image = Image.open(input_image_path)
    if input_image.size == (1, 1):
        raise ValueError("input image must contain more than 1 pixel")
    number_of_regions = min(int(number_of_regions),
                            number_of_colours(input_image))
    if key_image_filename:
        key_image_path = os.path.abspath(key_image_filename)
        key_image = Image.open(key_image_path)
    else:
        key_image = None
        number_of_regions = 1
    region_lists = create_region_lists(input_image, key_image,
                                       number_of_regions)
    seed(0)
    shuffle(region_lists)
    output_image = swap_pixels(input_image, region_lists)
    save_output_image(output_image, input_image_path)


def number_of_colours(image):
    return len(set(list(image.getdata())))


def create_region_lists(input_image, key_image, number_of_regions):
    template = create_template(input_image, key_image, number_of_regions)
    number_of_regions_created = len(set(template))
    region_lists = [[] for i in range(number_of_regions_created)]
    for i in range(len(template)):
        region = template[i]
        region_lists[region].append(i)
    odd_region_lists = [region_list for region_list in region_lists
                        if len(region_list) % 2]
    for i in range(len(odd_region_lists) - 1):
        odd_region_lists[i].append(odd_region_lists[i + 1].pop())
    return region_lists


def create_template(input_image, key_image, number_of_regions):
    if number_of_regions == 1:
        width, height = input_image.size
        return [0] * (width * height)
    else:
        resized_key_image = key_image.resize(input_image.size, Image.NEAREST)
        pixels = list(resized_key_image.getdata())
        pixel_measures = [measure(pixel) for pixel in pixels]
        distinct_values = list(set(pixel_measures))
        number_of_distinct_values = len(distinct_values)
        number_of_regions_created = min(number_of_regions,
                                        number_of_distinct_values)
        sorted_distinct_values = sorted(distinct_values)
        while True:
            values_per_region = (number_of_distinct_values /
                                 number_of_regions_created)
            value_to_region = {sorted_distinct_values[i]:
                               int(i // values_per_region)
                               for i in range(len(sorted_distinct_values))}
            pixel_regions = [value_to_region[pixel_measure]
                             for pixel_measure in pixel_measures]
            if no_small_pixel_regions(pixel_regions,
                                      number_of_regions_created):
                break
            else:
                number_of_regions_created //= 2
        return pixel_regions


def no_small_pixel_regions(pixel_regions, number_of_regions_created):
    counts = [0 for i in range(number_of_regions_created)]
    for value in pixel_regions:
        counts[value] += 1
    if all(counts[i] >= 256 for i in range(number_of_regions_created)):
        return True


def shuffle(region_lists):
    for region_list in region_lists:
        length = len(region_list)
        for i in range(length):
            j = randrange(length)
            region_list[i], region_list[j] = region_list[j], region_list[i]


def measure(pixel):
    '''Return a single value roughly measuring the brightness.

    Not intended as an accurate measure, simply uses primes to prevent two
    different colours from having the same measure, so that an image with
    different colours of similar brightness will still be divided into
    regions.
    '''
    if type(pixel) is int:
        return pixel
    else:
        r, g, b = pixel[:3]
        return r * 2999 + g * 5869 + b * 1151


def swap_pixels(input_image, region_lists):
    pixels = list(input_image.getdata())
    for region in region_lists:
        for i in range(0, len(region) - 1, 2):
            pixels[region[i]], pixels[region[i+1]] = (pixels[region[i+1]],
                                                      pixels[region[i]])
    scrambled_image = Image.new(input_image.mode, input_image.size)
    scrambled_image.putdata(pixels)
    return scrambled_image


def save_output_image(output_image, full_path):
    head, tail = os.path.split(full_path)
    if tail[:10] == 'scrambled_':
        augmented_tail = 'rescued_' + tail[10:]
    else:
        augmented_tail = 'scrambled_' + tail
    save_filename = os.path.join(head, augmented_tail)
    output_image.save(save_filename)


if __name__ == '__main__':
    import sys
    arguments = sys.argv[1:]
    if arguments:
        scramble(*arguments[:3])
    else:
        print('\n'
              'Arguments:\n'
              '    input image          (required)\n'
              '    key image            (optional, default None)\n'
              '    number of regions    '
              '(optional maximum - will be as high as practical otherwise)\n')

Запись изображения JPEG

Файлы .jpg обрабатываются очень быстро, но за счет слишком горячей работы. Это оставляет сгоревшее изображение после восстановления оригинала:

JPG сжечь

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

Преобразование в формат .png (или любой другой формат без потерь) перед скремблированием гарантирует, что нешифрованное изображение идентично оригиналу без прожигания или искажения:

PNG без ожога

Маленькие детали

  • Минимальный размер 256 пикселей накладывается на регионы. Если изображение было разрешено разделить на области, которые слишком малы, то исходное изображение все еще будет частично видимым после скремблирования.
  • Если существует более одной области с нечетным числом пикселей, то один пиксель из второй области переназначается первому и т. Д. Это означает, что может быть только одна область с нечетным количеством пикселей, и поэтому только один пиксель останется расшифрованным.
  • Существует третий необязательный аргумент, который ограничивает количество регионов. Например, установка этого параметра на 2 даст два скремблированных изображения. Это может выглядеть лучше или хуже в зависимости от изображения. Если здесь указано число, изображение можно восстановить только с использованием того же номера снова.
  • Количество различных цветов в исходном изображении также ограничивает количество областей. Если исходное изображение имеет два тона, то независимо от ключевого изображения или третьего аргумента может быть не более 2 областей.

2
+1 Аплодисменты! Я смутно думал об этом, но это оказалось слишком сложным для реализации.
edc65

1
Это великолепно. У меня есть отправленная запись, но мне больше нравится ваша из-за ключевой функции изображения.
Тодд Леман,

Мне было бы любопытно, как выглядят эти два изображения друг против друга: lardlad.com/assets/wallpaper/simpsons1920.jpg и blogs.nd.edu/oblation/files/2013/09/BreakingBad.jpg (уменьшено до 720x450 или все, что имеет смысл, и, конечно, предварительно преобразован в PNG, чтобы избежать записи JPEG).
Тодд Леман

2
@ToddLehman мой алгоритм ограничен необходимостью быть обратным. Если вы хотите увидеть некоторые действительно интересные подходы к перетасовке одного изображения для напоминания другого, вам следует взглянуть на американскую готику в палитре Моны Лизы . Некоторые из этих программ будут делать удивительные вещи с изображениями, которые вы упоминаете.
Трихоплакс

2
Ключевая особенность изображения ставит эту голову и плечи выше остальных.
Джек Эйдли,

33

Вот неслучайное преобразование для изменения

  1. Поместите все четные столбцы слева и все нечетные столбцы справа.
  2. повторить nxраз
  3. сделать то же самое для рядов nyраз

Преобразование является почти самообращенным, повторение преобразования в общей сложности несколько size_xраз (в направлении x) возвращает исходное изображение. Я не разобрался с точной математикой, но использование целых кратных int(log_2(size_x))дает лучшую перетасовку с наименьшими призрачными изображениями

тасованные горы введите описание изображения здесь

from numpy import *
from pylab import imread, imsave

def imshuffle(im, nx=0, ny=0):
    for i in range(nx):
        im = concatenate((im[:,0::2], im[:,1::2]), axis=1)
    for i in range(ny):
        im = concatenate((im[0::2,:], im[1::2,:]), axis=0)
    return im

im1 = imread('circles.png')
im2 = imread('mountain.jpg')

imsave('s_circles.png', imshuffle(im1, 7,7))
imsave('s_mountain.jpg', imshuffle(im2, 8,9))

Вот так выглядят первые шаги 20 итераций (nx = ny, обратите внимание на эффект разных разрешений) введите описание изображения здесь


7
Это действительно крутой алгоритм. И вы должны полностью получить бонус за использование фотографии Lena Söderberg. :)
Тодд Леман

Всегда upvote Лена

24

Mathematica

Это довольно просто. Я выбираю 5 * nPixelsслучайные пары координат и меняю местами эти два пикселя (что полностью скрывает картинку). Чтобы расшифровать его, я делаю то же самое в обратном порядке. Конечно, мне нужно запустить PRNG, чтобы получить одинаковые пары координат на обоих шагах.

scramble[image_] := Module[
   {data, h, w, temp},
   data = ImageData@image;
   {h, w} = Most@Dimensions@data;
   SeedRandom[42];
   (
      temp = data[[#[[1]], #[[2]]]];
      data[[#[[1]], #[[2]]]] = data[[#2[[1]], #2[[2]]]];
      data[[#2[[1]], #2[[2]]]] = temp;
      ) & @@@
    Partition[
     Transpose@{RandomInteger[h - 1, 10*h*w] + 1, 
       RandomInteger[w - 1, 10*h*w] + 1}, 2];
   Image@data
   ];
unscramble[image_] := Module[
   {data, h, w, temp},
   data = ImageData@image;
   {h, w} = Most@Dimensions@data;
   SeedRandom[42];
   (
      temp = data[[#[[1]], #[[2]]]];
      data[[#[[1]], #[[2]]]] = data[[#2[[1]], #2[[2]]]];
      data[[#2[[1]], #2[[2]]]] = temp;
      ) & @@@
    Reverse@
     Partition[
      Transpose@{RandomInteger[h - 1, 10*h*w] + 1, 
        RandomInteger[w - 1, 10*h*w] + 1}, 2];
   Image@data
   ];

Единственная разница между двумя функциями заключается Reverse@в unscramble. Обе функции принимают фактический объект изображения. Вы можете использовать их следующим образом:

in = Import["D:\\Development\\CodeGolf\\image-scrambler\\circles.png"]
scr = scramble[im]
out = unscramble[scr]

outи inидентичны. Вот как это scrвыглядит:

введите описание изображения здесь введите описание изображения здесь


4
Большой! Единственная проблема заключается в том, что безопаснее создавать PRNG самостоятельно, потому что если через некоторое время Mathematica решит изменить алгоритм PRNG, это не будет декодировать старые закодированные изображения!
Сомниум

1
Приятно. Вы должны быть в состоянии достичь того же результата с Permute и FindPermutation.
DavidC

Я не уверен, что понимаю. Вы можете ввести точную перестановку, которую вы хотите, в виде списка циклов.
DavidC

@DavidCarraher Хм, интересно. Разве я не должен был помнить оригинальную перестановку для использования FindPermutationхотя?
Мартин Эндер

Или может быть что-то, что {c, a, b}[[{2, 3, 1}]]можно использовать?
Сомниум

22

C # (+ бонус за симметричный алгоритм)

Это работает, находя xтакое, что x^2 == 1 mod (number of pixels in image), а затем умножая индекс каждого пикселя на x, чтобы найти его новое местоположение. Это позволяет использовать точно такой же алгоритм для шифрования и расшифровки изображения.

using System.Drawing;
using System.IO;
using System.Numerics;

namespace RearrangePixels
{
    class Program
    {
        static void Main(string[] args)
        {
            foreach (var arg in args)
                ScrambleUnscramble(arg);
        }

        static void ScrambleUnscramble(string fileName)
        {
            using (var origImage = new Bitmap(fileName))
            using (var newImage = new Bitmap(origImage))
            {
                BigInteger totalPixels = origImage.Width * origImage.Height;
                BigInteger modSquare = GetSquareRootOf1(totalPixels);
                for (var x = 0; x < origImage.Width; x++)
                {
                    for (var y = 0; y < origImage.Height; y++)
                    {
                        var newNum = modSquare * GetPixelNumber(new Point(x, y), origImage.Size) % totalPixels;
                        var newPoint = GetPoint(newNum, origImage.Size);
                        newImage.SetPixel(newPoint.X, newPoint.Y, origImage.GetPixel(x, y));
                    }
                }
                newImage.Save("scrambled-" + Path.GetFileName(fileName));
            }
        }

        static BigInteger GetPixelNumber(Point point, Size totalSize)
        {
            return totalSize.Width * point.Y + point.X;
        }

        static Point GetPoint(BigInteger pixelNumber, Size totalSize)
        {
            return new Point((int)(pixelNumber % totalSize.Width), (int)(pixelNumber / totalSize.Width));
        }

        static BigInteger GetSquareRootOf1(BigInteger modulo)
        {
            for (var i = (BigInteger)2; i < modulo - 1; i++)
            {
                if ((i * i) % modulo == 1)
                    return i;
            }
            return modulo - 1;
        }
    }
}

первое тестовое изображение, зашифрованное

второе тестовое изображение, зашифрованное


1
Умный) Будет ли всегда решение этого уравнения конгруэнтности?
Сомниум

1
@ user2992539 Всегда найдутся тривиальные решения 1(исходное изображение) и modulo-1(перевернутое / перевернутое изображение). Большинство чисел имеют нетривиальные решения, но , похоже , есть некоторые исключения . (связано с первичной факторизацией modulo)
Тим С.

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

Правильно: 1выводит исходное изображение и -1выводит, например, imgur.com/EiE6VW2
Тим С.

19

C #, самообратный, нет случайности

Если исходное изображение имеет размеры, равные степени двух, то каждая строка и столбец заменяются строкой и столбцом, имеющим инвертированный битовый шаблон, например, для изображения шириной 256, тогда строка 0xB4 заменяется строкой 0x2D. Изображения других размеров делятся на прямоугольники со сторонами степеней 2.

namespace CodeGolf
{
    class Program
    {
        static void Main(string[] args)
        {
            foreach (var arg in args)
                Scramble(arg);
        }

        static void Scramble(string fileName)
        {
            using (var origImage = new System.Drawing.Bitmap(fileName))
            using (var tmpImage = new System.Drawing.Bitmap(origImage))
            {
                {
                    int x = origImage.Width;
                    while (x > 0) {
                       int xbit = x & -x;
                        do {
                            x--;
                            var xalt = BitReverse(x, xbit);
                            for (int y = 0; y < origImage.Height; y++)
                                tmpImage.SetPixel(xalt, y, origImage.GetPixel(x, y));
                        } while ((x & (xbit - 1)) != 0);
                    }
                }
                {
                    int y = origImage.Height;
                    while (y > 0) {
                        int ybit = y & -y;
                        do {
                            y--;
                            var yalt = BitReverse(y, ybit);
                            for (int x = 0; x < origImage.Width; x++)
                                origImage.SetPixel(x, yalt, tmpImage.GetPixel(x, y));
                        } while ((y & (ybit - 1)) != 0);
                    } 
                }
                origImage.Save(System.IO.Path.GetFileNameWithoutExtension(fileName) + "-scrambled.png");
            }
        }

        static int BitReverse(int n, int bit)
        {
            if (bit < 4)
                return n;
            int r = n & ~(bit - 1);
            int tmp = 1;
            while (bit > 1) {
                bit >>= 1;
                if ((n & bit) != 0)
                    r |= tmp;
                tmp <<= 1;
            }
            return r;
        }
    }
}

Первое изображение:

Скремблированное первое изображение

Второе изображение:

Скремблированное второе изображение


2
Мне нравится выход "плед" на этом.
Брайан Роджерс

14

C #

Тот же метод для шифрования и расшифровки. Буду признателен за предложения по улучшению этого.

using System;
using System.Drawing;
using System.Linq;

public class Program
{
    public static Bitmap Scramble(Bitmap bmp)
    {
        var res = new Bitmap(bmp);
        var r = new Random(1);

        // Making lists of even and odd numbers and shuffling them
        // They contain numbers between 0 and picture.Width (or picture.Height)
        var rX = Enumerable.Range(0, bmp.Width / 2).Select(x => x * 2).OrderBy(x => r.Next()).ToList();
        var rrX = rX.Select(x => x + 1).OrderBy(x => r.Next()).ToList();
        var rY = Enumerable.Range(0, bmp.Height / 2).Select(x => x * 2).OrderBy(x => r.Next()).ToList();
        var rrY = rY.Select(x => x + 1).OrderBy(x => r.Next()).ToList();

        for (int y = 0; y < bmp.Height; y++)
        {
            for (int x = 0; x < rX.Count; x++)
            {
                // Swapping pixels in a row using lists rX and rrX
                res.SetPixel(rrX[x], y, bmp.GetPixel(rX[x], y));
                res.SetPixel(rX[x], y, bmp.GetPixel(rrX[x], y));
            }
        }
        for (int x = 0; x < bmp.Width; x++)
        {
            for (int y = 0; y < rY.Count; y++)
            {
                // Swapping pixels in a column using sets rY and rrY
                var px = res.GetPixel(x, rrY[y]);
                res.SetPixel(x, rrY[y], res.GetPixel(x, rY[y]));
                res.SetPixel(x, rY[y], px);
            }
        }

        return res;
    }
}

Вывод результатов в психоделический плед Первый Второй


Приятно, что в этом есть какой-то полосатый рисунок)
Сомниум

1
Можете ли вы поменять местами 2 изображения? В этом вопросе изображение гор является первым.
AL

1
Не могли бы вы включить краткое объяснение алгоритма?
Трихоплакс

14

Python 2 (обратный, без случайности, контекстно-зависимый)

Это не принесет никаких призов за «наименее узнаваемый», но, возможно, он может быть оценен как «интересный». :-)

Я хотел сделать что-то контекстно-зависимое, где скремблирование пикселей на самом деле зависит от самого изображения.

Идея довольно проста: отсортировать все пиксели по некоторому произвольному значению, полученному из цвета пикселя, а затем поменять местами позиции первого пикселя в этом списке с последним, второго с второго до последнего и т. Д.

К сожалению, в этом простом подходе есть проблема с пикселями одного цвета, поэтому, чтобы сделать его самообращенным, моя программа стала немного сложнее ...

from PIL import Image

img = Image.open('1.png', 'r')
pixels = img.load()
size_x, size_y = img.size

def f(colour):
    r,g,b = colour[:3]
    return (abs(r-128)+abs(g-128)+abs(b-128))//128

pixel_list = [(x,y,f(pixels[x,y])) for x in xrange(size_x) for y in xrange(size_y)]
pixel_list.sort(key=lambda x: x[2])
print "sorted"

colours = {}
for p in pixel_list:
    if p[2] in colours:
        colours[p[2]] += 1
    else:
        colours[p[2]] = 1
print "counted"

for n in set(colours.itervalues()):
    pixel_group = [p for p in pixel_list if colours[p[2]]==n]
    N = len(temp_list)
    for p1, p2 in zip(pixel_group[:N//2], pixel_group[-1:-N//2:-1]):
        pixels[p1[0],p1[1]], pixels[p2[0],p2[1]] = pixels[p2[0],p2[1]], pixels[p1[0],p1[1]]
print "swapped"

img.save('1scrambled.png')
print "saved"

Это результат: (Абс (г-128) + ABS (G-128) + ABS (б-128)) // 128 (Абс (г-128) + ABS (G-128) + ABS (б-128)) // 128

Вы можете достичь совершенно разных результатов, изменив хеш-функцию f:

  • r-g-b:

    RGB

  • r+g/2.**8+b/2.**16:

    г + г / 2. ** 8 + Ь / 2. ** 16

  • math.sin(r+g*2**8+b*2**16):

    Math.sin (г + д * 2 ** 8 + Ь * 2 ** 16)

  • (r+g+b)//600:

    (Г + д + б) // 600

  • 0:

    0


3
ВАУ! Это здорово !!! Хорошо сделано!
Тодд Леман

1
Это самый интересный на данный момент. Хорошая работа!
bebe

12

Mathematica (+ бонус)

Это сворачивает цветовые каналы и скремблирует изображение как один длинный список данных. В результате получается еще менее узнаваемая зашифрованная версия, поскольку она не имеет такого же цветового распределения, как оригинал (поскольку эти данные тоже были зашифрованы). Это наиболее очевидно на втором скремблированном изображении, но если вы присмотритесь, вы увидите тот же эффект и на первом. Функция является собственной обратной.

Был комментарий, что это может быть недопустимо, потому что он скремблирует на канал. Я думаю, что это должно быть, но это не имеет большого значения. Единственное изменение, необходимое для шифрования целых пикселей (вместо каждого канала), должно быть изменено Flatten @ xна Flatten[x, 1]:)

ClearAll @ f;

f @ x_ := 
  With[
    {r = SeedRandom[Times @@ Dimensions @ x], f = Flatten @ x},
    ArrayReshape[
      Permute[f, Cycles @ Partition[RandomSample @ Range @ Length @ f, 2]],
      Dimensions @ x]]

объяснение

Определяет функцию, fкоторая принимает 2-мерный массив x. Функция использует произведение размеров изображения в качестве случайного начального числа, а затем выравнивает массив до одномерного списка f(локально затененного). Затем он создает список в форме, {1, 2, ... n}где nесть длина f, случайным образом переставляет этот список, разбивает его на сегменты по 2 (например, {{1, 2}, {3, 4}, ...}(отбрасывая последнее число, если оба измерения нечетные), а затем переставляет f, меняя значения на позиции, указанные в каждом только что созданном подсписке, и, наконец, он преобразует пермутированный список обратно в исходные размеры x. Он скремблирует на канал, потому что помимо свертывания размеров изображенияFlattenКоманда также сворачивает данные канала в каждом пикселе. Функция является собственной обратной, потому что циклы включают в себя только два пикселя каждый.

использование

img1=Import@"http://i.stack.imgur.com/2C2TY.jpg"//ImageData;
img2=Import@"http://i.stack.imgur.com/B5TbK.png"//ImageData;

f @ img1 // Image

введите описание изображения здесь

f @ f @ img1 // Image

введите описание изображения здесь

f @ img2 // Image

введите описание изображения здесь

f @ f @ img2 // Image

введите описание изображения здесь

Вот с помощью Flatten[x, 1].

g@x_ := With[{r = SeedRandom[Times @@ Dimensions @ x], f = Flatten[x, 1]}, 
  ArrayReshape[
   Permute[f, Cycles@Partition[RandomSample@Range@Length@f, 2]], 
   Dimensions@x]]

g@img2 // Image

введите описание изображения здесь


1
Я предполагаю, что это не соответствует критериям, так как оно меняется в масштабе меньше пикселя.
Трихоплакс

1
Я не думаю, что это правильный ответ, но мне тоже очень нравится. Это захватывающий поворот, так что +1 в любом случае ...
trichoplax

1
@githubphagocyte Смотрите обновление :)
mfvonh

Отлично - я снова потянулся к +1, но, конечно, я не могу сделать это дважды ...
trichoplax

1
@ githubphagocyte Да, я забыл, что короткий синтаксис может быть странным. Да. f @ f @ img1 // Imageесть (в полном синтаксисе)Image[f[f[img1]]]
mfvonh

10

Матлаб (+ бонус)

Я в основном переключаю положение двух пикселей наугад и помечаю каждый пиксель, который был переключен, чтобы он не переключался снова. Тот же сценарий можно использовать снова для «расшифровки», потому что я каждый раз сбрасываю генератор случайных чисел. Это делается до тех пор, пока почти все пиксели не переключатся (поэтому размер шага больше 2)

РЕДАКТИРОВАТЬ: Просто увидел, что Мартин Бюттнер использовал аналогичный подход - я не собирался копировать идею - я начал писать свой код, когда не было ответов, извините за это. Я все еще думаю, что моя версия использует несколько разных идей =) (И мой алгоритм гораздо более неэффективен, если вы посмотрите на бит, где выбираются две случайные координаты ^^)

Картинки

1 2

Код

img = imread('shuffle_image2.bmp');
s = size(img)
rand('seed',0)
map = zeros(s(1),s(2));
for i = 1:2.1:s(1)*s(2) %can use much time if stepsize is 2 since then every pixel has to be exchanged
    while true %find two unswitched pixels
        a = floor(rand(1,2) .* [s(1),s(2)] + [1,1]);
        b = floor(rand(1,2) .* [s(1),s(2)] + [1,1]);
        if map(a(1),a(2)) == 0 && map(b(1),b(2)) == 0
            break
        end
    end
    %switch
    map(a(1),a(2)) = 1;
    map(b(1),b(2)) = 1;
    t = img(a(1),a(2),:);
    img(a(1),a(2),:) = img(b(1),b(2),:);
    img(b(1),b(2),:) = t;
end
image(img)
imwrite(img,'output2.png')

Я не до конца понимаю, расшифрует ли ваш код, примененный второй раз к зашифрованному изображению?
Сомниум

2
Точно: каждый раз точно два пикселя меняются местами, и не будут заменены снова в течение всего процесса. Поскольку «случайные» числа оба раза одинаковы (из-за сброса генератора случайных чисел), пары пикселей будут заменены. (ГСЧ всегда полагается на предыдущее сгенерированное число для генерации следующего, надеюсь, это ясно.)
flawr

1
Ха, на самом деле это была моя первоначальная идея, но потом я не мог позаботиться о том, чтобы каждый пиксель менялся местами ровно один раз, потому что мне нужно было приступить к работе. : D +1!
Мартин Эндер

3
@ user2992539 Хорошо, посмотрите Octave, который является хорошей альтернативой для Matlab с открытым исходным кодом, и вы можете запустить 99% кода Matlab непосредственно в октаве.
Flawr

2
Я думаю, что если вы действительно сильно прищуриваетесь на своих изображениях, вы все равно можете увидеть некоторую структуру из входных данных (что связано с не перемещением всех пикселей). Я предполагаю, что если вы изменили алгоритм выбора для запуска в O (1) вместо O (∞), вы могли бы это исправить. ;)
Мартин Эндер

10

Mathematica - используйте перестановку для шифрования и обратную для расшифровки.

Изображение jpg - это трехмерный массив {r,g,b}цветов пикселей. (3 измерения структурируют набор пикселей по строке, столбцу и цвету). Он может быть сведен в список {r,g,b}троек, затем переставлен согласно «известному» списку циклов и, наконец, повторно собран в массив исходных измерений. Результатом является зашифрованное изображение.

Unscramble берет зашифрованное изображение и обрабатывает его в обратной последовательности списка циклов. Это выводит, да, оригинальное изображение.

Таким образом, одна функция (в данном случае scramble) служит для скремблирования, а также для скремблирования пикселей в изображении.

Изображение вводится вместе с начальным номером (чтобы гарантировать, что генератор случайных чисел будет в одном и том же состоянии для скремблирования и дешифрования). Когда параметр reverse является False, функция будет шифроваться. Когда это правда, функция расшифровывается.


свалка

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

расшифровывать

Эта же функция scrambleиспользуется для расшифровки. Однако порядок списка циклов меняется на противоположный.

scramble[img_,s_,reverse_:False,imgSize_:300]:=
  Module[{i=ImageData[img],input,r},input=Flatten[i,1];SeedRandom[s];
  r=RandomSample@Range[Length[input]];Image[ArrayReshape[Permute[input,
  Cycles[{Evaluate@If[reverse,Reverse@r,r]}]],Dimensions[i]],ImageSize->imgSize]]

Примеры

Это же семя (37) используется для скремблирования и скремблирования.

Это производит зашифрованное изображение горы. На рисунке ниже показано, что переменная scrambledMount может быть заменена реальным изображением горной сцены.

scrambledMount=scramble[mountain, 37, True]

mount1


Теперь мы запускаем обратное; scrambledMount введен и исходное изображение восстановлено.

 scramble[scrambledMount, 37, True]

mount2


То же самое для кругов:

circles1


 scramble[scrambledCircles, 37, True]

circles2


Я не вижу, как изображение может быть трехмерным массивом.
edc65

1
@ edc65, строки х столбцы х цветов. Изображение горы составляет 422 строки по 800 колонок по 3 цвета. Если массив сглаживается, он выдает 1012800 фрагментов данных в виде одномерного массива, то есть в виде списка.
DavidC

@ edc65 Я должен добавить, что Permute использовался для перестановки цветов как тройки rgb. Я больше не сглаживал это, потому что не интересовался внесением каких-либо изменений в список цветов любого пикселя. Если вы рассматриваете информацию rgb, {r, g, b} как элемент, то речь идет о двумерном массиве. С этой точки зрения имеет смысл поднять вопрос (каким образом изображение может быть трехмерным массивом?), Который вы подняли. Фактически, может быть более нормальным рассматривать изображение как двумерный массив, не принимая во внимание тот факт, что элементы rgb добавляют другое измерение.
DavidC

10

питон

Мне нравится эта головоломка, он показался мне интересным, и я пришел с функцией обтекания и движения, чтобы применить ее к изображению.

Wraped

Я читаю картинку как текст (слева направо, вверх и вниз) и пишу ее как раковину улитки.

Эта функция является циклической: в N есть f, f (n) (x) = x, например, для изображения 4 * 2, f (f (f (x))) = x

движение

Я беру случайное число и перемещаю каждый столбец и строку из него

Код

# Opening and creating pictures
img = Image.open("/home/faquarl/Bureau/unnamed.png")
PM1 = img.load()
(w,h) = img.size
img2 = Image.new( 'RGBA', (w,h), "black") 
PM2 = img2.load()
img3 = Image.new( 'RGBA', (w,h), "black") 
PM3 = img3.load()

# Rotation
k = 0
_i=w-1
_j=h-1
_currentColMin = 0
_currentColMax = w-1
_currentLigMin = 0
_currentLigMax = h-1
_etat = 0
for i in range(w):
    for j in range(h):
        PM2[_i,_j]=PM1[i,j]
        if _etat==0:
            if _currentColMax == _currentColMin:
                _j -= 1
                _etat = 2
            else:
                _etat = 1
                _i -= 1
        elif _etat==1:
            _i -= 1
            if _j == _currentLigMax and _i == _currentColMin:
                _etat = 2
        elif _etat==2:
            _j -= 1
            _currentLigMax -= 1
            if _j == _currentLigMin and _i == _currentColMin:
                _etat = 5
            else:
                _etat = 3
        elif _etat==3:
            _j -= 1
            if _j == _currentLigMin and _i == _currentColMin:
                _etat = 4
        elif _etat==4:
            _i += 1
            _currentColMin += 1
            if _j == _currentLigMin and _i == _currentColMax:
                _etat = 7
            else:
                _etat = 5
        elif _etat==5:
            _i += 1
            if _j == _currentLigMin and _i == _currentColMax:
                _etat = 6
        elif _etat==6:
            _j += 1
            _currentLigMin += 1
            if _j == _currentLigMax and _i == _currentColMax:
                _etat = 1
            else:
                _etat = 7
        elif _etat==7:
            _j += 1
            if _j == _currentLigMax and _i == _currentColMax:
                _etat = 8
        elif _etat==8:
            _i -= 1
            _currentColMax -= 1
            if _j == _currentLigMax and _i == _currentColMin:
                _etat = 3
            else:
                _etat = 1
        k += 1
        if k == w * h:
            i = w
            j = h
# Movement
if w>h:z=w
else:z=h
rand.seed(z)
a=rand.randint(0,h)
for i in range(w):
  for j in range(h):
  if i%2==0:
    PM3[(i+a)%w,(j+a)%h]=PM2[i,j]
  else:
    PM3[(i-a)%w,(j-a)%h]=PM2[i,j]
# Rotate Again

Фотографий

Первый поворот: введите описание изображения здесь

затем перестановка: введите описание изображения здесь

И с последним поворотом: введите описание изображения здесь

Что касается другого примера: введите описание изображения здесь


2
Как восстановить исходное изображение?
трихоплакс

Если это просто вращение, я могу сделать это определенное количество времени (зависит от размера). Однако, если бы у меня были перестановки, я не уверен, что это циклично, поэтому у меня просто есть вторая функция, единственное изменение которой состоит в том, что PM2 [_i, _j] = PM1 [i, j] стал PM2 [i, j] = PM1 [ _i, _j] и PM3 [(i + a)% w, (j + a)% h] = PM2 [i, j] стал PM3 [(ia)% w, (ja)% h] = PM2 [i, J]. Я ищу способ сделать это, не меняя эти две строки
Faquarl

8

VB.NET (+ бонус)

Это использует идею flawr, благодаря ему, однако это использует другой алгоритм обмена и проверки. Программа кодирует и декодирует одинаково.

Imports System

Module Module1

    Sub swap(ByVal b As Drawing.Bitmap, ByVal i As Integer, ByVal j As Integer)
        Dim c1 As Drawing.Color = b.GetPixel(i Mod b.Width, i \ b.Width)
        Dim c2 As Drawing.Color = b.GetPixel(j Mod b.Width, j \ b.Width)
        b.SetPixel(i Mod b.Width, i \ b.Width, c2)
        b.SetPixel(j Mod b.Width, j \ b.Width, c1)
    End Sub

    Sub Main(ByVal args() As String)
        For Each a In args
            Dim f As New IO.FileStream(a, IO.FileMode.Open)
            Dim b As New Drawing.Bitmap(f)
            f.Close()
            Dim sz As Integer = b.Width * b.Height - 1
            Dim w(sz) As Boolean
            Dim r As New Random(666)
            Dim u As Integer, j As Integer = 0
            Do While j < sz
                Do
                    u = r.Next(0, sz)
                Loop While w(u)
                ' swap
                swap(b, j, u)
                w(j) = True
                w(u) = True
                Do
                    j += 1
                Loop While j < sz AndAlso w(j)
            Loop
            b.Save(IO.Path.ChangeExtension(a, "png"), Drawing.Imaging.ImageFormat.Png)
            Console.WriteLine("Done!")
        Next
    End Sub

End Module

Выходные изображения:


8

После напоминания о том, что это собирается поменять пиксели, а не изменять их, вот мое решение для этого:

Яичница: введите описание изображения здесь

Восстановлен введите описание изображения здесь

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

public class ImageScramble {

  public static void main(String[] args) throws IOException {
    if (args.length < 2) {
      System.err.println("Usage: ImageScramble <fileInput> <fileOutput>");
    } else {
      // load image
      final String extension = args[0].substring(args[0].lastIndexOf('.') + 1);
      final BufferedImage image = ImageIO.read(new File(args[0]));
      final int[] pixels = image.getRGB(0, 0, image.getWidth(), image.getHeight(), null, 0, image.getWidth());

      // create randomized swap list
      final ArrayList<Integer> indexes = IntStream.iterate(0, i -> i + 1).limit(pixels.length).collect(ArrayList::new, ArrayList::add, ArrayList::addAll);
      Collections.shuffle(indexes, new Random(1337));

      // swap all pixels at index n with pixel at index n+1
      int tmp;
      for (int i = 0; i < indexes.size(); i += 2) {
        tmp = pixels[indexes.get(i)];
        pixels[indexes.get(i)] = pixels[indexes.get(i + 1)];
        pixels[indexes.get(i + 1)] = tmp;
      }

      // write image to disk
      final BufferedImage imageScrambled = new BufferedImage(image.getWidth(), image.getHeight(), image.getType());
      imageScrambled.setRGB(0, 0, imageScrambled.getWidth(), imageScrambled.getHeight(), pixels, 0, imageScrambled.getWidth());
      ImageIO.write(imageScrambled, extension, new File(args[1]));
    }
  }
}

Обратите внимание, что использование этого алгоритма в формате сжатия с потерями не приведет к тому же результату, так как формат изображения изменит данные. Это должно хорошо работать с любым кодеком без потерь, таким как PNG.


8

Mathematica

Мы определяем вспомогательную функцию hи функцию скремблирования scrambleкак:

h[l_List, n_Integer, k_Integer: 1] := 
  With[{ m = Partition[l, n, n, 1, 0] }, 
    Flatten[
      Riffle[
        RotateLeft[ m[[ ;; , {1} ]] , k ],
        m[[ ;; , 2;; ]]
      ], 1
    ] [[ ;; Length[l] ]]
  ];

scramble[img_Image, k_Integer] :=
  Module[{ list , cNum = 5 },
    Which[
      k > 0,    list = Prime@Range[cNum],
      k < 0,    list = Reverse@Prime@Range[cNum],
      True,     list = {}
    ];
    Image[
      Transpose[
        Fold[ h[ #1, #2, k ] &, #, list ] & /@
        Transpose[
          Fold[ h[#1, #2, k] &, #, list ] & /@ ImageData[img]
        ]
      ]
    ]
  ];

После загрузки изображения, вы можете назвать , scramble[img, k]где kлюбое целое число, чтобы засекретить изображение. Звоню снова с -kволей. (Если kесть 0, то никаких изменений не производится.) Как правило, kдолжно быть выбрано что-то вроде 100, что дает довольно скремблированное изображение:

Пример вывода 1

Пример вывода 2


7

Matlab: скремблирование строк и столбцов на основе инвариантов суммы строк / столбцов

Это казалось забавной головоломкой, поэтому я подумал об этом и придумал следующую функцию. Он основан на неизменности сумм значений пикселей в строках и столбцах во время циклического сдвига: он сдвигает каждую строку, затем каждый столбец на общую сумму значений пикселей строки / столбца (предполагая, что uint8 для целого числа в переменной shift) ). Это можно затем изменить путем смещения каждого столбца, а затем строки на их суммарное значение в противоположном направлении.

Он не такой красивый, как другие, но мне нравится, что он не случайный и полностью определяется изображением - без выбора параметров.

Первоначально я разработал его для сдвига каждого цветового канала в отдельности, но затем заметил, что спецификация перемещает только полные пиксели.

function pic_scramble(input_filename)
i1=imread(input_filename);
figure;
subplot(1,3,1);imagesc(i1);title('Original','fontsize',20);

i2=i1;
for v=1:size(i1,1)
    i2(v,:,:)=circshift(i2(v,:,:),sum(sum(i2(v,:,:))),2);
end
for w=1:size(i2,2)
    i2(:,w,:)=circshift(i2(:,w,:),sum(sum(i2(:,w,:))),1);
end
subplot(1,3,2);imagesc(i2);title('Scrambled','fontsize',20);

i3=i2;
for w=1:size(i3,2)
    i3(:,w,:)=circshift(i3(:,w,:),-1*sum(sum(i3(:,w,:))),1);
end
for v=1:size(i3,1)
    i3(v,:,:)=circshift(i3(v,:,:),-1*sum(sum(i3(v,:,:))),2);
end
subplot(1,3,3);imagesc(i3);title('Recovered','fontsize',20);

Первое тестовое изображение Secont тестовое изображение


6

Ява

Эта программа случайным образом меняет пиксели (создает отображение пиксель в пиксель), но вместо случайной функции она использует Math.sin () (целое число x). Это полностью обратимо. С тестовыми изображениями это создает некоторые образцы.

Параметры: целое число (количество проходов, отрицательное число для реверса, 0 ничего не делает), входной маг и выходное изображение (могут совпадать). Выходной файл должен быть в формате, который использует сжатие без потерь.

1 проход: введите описание изображения здесь введите описание изображения здесь

100 проходов (это займет несколько минут): введите описание изображения здесь введите описание изображения здесь

Код:

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;

public class Test{

public static void main(String... args) {
    String in = "image.png";
    String out = in;
    int passes = 0;
    if (args.length < 1) {
        System.out.println("no paramem encryptimg, 1 pass, reading and saving image.png");
        System.out.println("Usage: pass a number. Negative - n passes of decryption, positive - n passes of encryption, 0 - do nothing");
    } else {
        passes = Integer.parseInt(args[0]);
        if (args.length > 1) {
            in = args[1];
        }
        if(args.length > 2){
            out = args[2];
        }
    }
    boolean encrypt = passes > 0;
    passes = Math.abs(passes);
    for (int a = 0; a < passes; a++) {
        BufferedImage img = null;
        try {
            img = ImageIO.read(new File(a == 0 ? in : out));
        } catch (IOException e) {
            e.printStackTrace();
            return;
        }
        int pixels[][] = new int[img.getWidth()][img.getHeight()];
        int[][] newPixels = new int[img.getWidth()][img.getHeight()];
        for (int x = 0; x < pixels.length; x++) {
            for (int y = 0; y < pixels[x].length; y++) {
                pixels[x][y] = img.getRGB(x, y);
            }
        }
        int amount = img.getWidth() * img.getHeight();
        int[] list = new int[amount];
        for (int i = 0; i < amount; i++) {
            list[i] = i;
        }
        int[] mapping = new int[amount];
        for (int i = amount - 1; i >= 0; i--) {
            int num = (Math.abs((int) (Math.sin(i) * amount))) % (i + 1);
            mapping[i] = list[num];
            list[num] = list[i];
        }
        for (int xz = 0; xz < amount; xz++) {
            int x = xz % img.getWidth();
            int z = xz / img.getWidth();
            int xzMap = mapping[xz];
            int newX = xzMap % img.getWidth();
            int newZ = xzMap / img.getWidth();
            if (encrypt) {
                newPixels[x][z] = pixels[newX][newZ];
            } else {
                newPixels[newX][newZ] = pixels[x][z];
            }
        }
        BufferedImage newImg = new BufferedImage(img.getWidth(), img.getHeight(), BufferedImage.TYPE_INT_RGB);
        for (int x = 0; x < pixels.length; x++) {
            for (int y = 0; y < pixels[x].length; y++) {
                newImg.setRGB(x, y, newPixels[x][y]);
            }
        }

        try {
            String[] s = out.split("\\.");
            ImageIO.write(newImg, s[s.length - 1],
                    new File(out));
        } catch (IOException e) {
            e.printStackTrace();
            return;
        }
    }
}
}

6

Python 2.7 с PIL

Немного опоздал на вечеринку, но я подумал, что было бы интересно превратить изображения в пледы (и обратно, конечно). Сначала мы смещаем столбцы вверх или вниз в 4 раза по количеству столбцов (четные столбцы вниз, нечетные столбцы вверх). Затем мы сдвигаем строки влево или вправо в 4 раза по числу строк (четные столбцы влево, нечетные столбцы вправо).

Результат довольно тартанский.

Чтобы изменить, мы просто делаем это в обратном порядке и сдвигаемся на противоположную величину.

Код

from PIL import Image

def slideColumn (pix, tpix, x, offset, height):
  for y in range(height):
    tpix[x,(offset+y)%height] = pix[x,y]

def slideRow (pix, tpix, y, offset, width):
  for x in range(width):
    tpix[(offset+x)%width,y] = pix[x,y]

def copyPixels (source, destination, width, height):
  for x in range(width):
    for y in range(height):
      destination[x,y]=source[x,y]

def shuffleHorizontal (img, tmpimg, factor, encoding):
  xsize,ysize = img.size
  pix = img.load()
  tpix = tmpimg.load()
  for y in range(ysize):
    offset = y*factor
    if y%2==0:
      offset = xsize-offset
    offset = (xsize + offset) % xsize
    if encoding:
      slideRow(pix,tpix,y,offset,xsize)
    else:
      slideRow(pix,tpix,y,-offset,xsize)
  copyPixels(tpix,pix,xsize,ysize)

def shuffleVertical (img, tmpimg, factor, encoding):
  xsize,ysize = img.size
  pix = img.load()
  tpix = tmpimg.load()
  for x in range(xsize):
    offset = x*factor
    if x%2==0:
      offset = ysize-offset
    offset = (ysize + offset) % ysize
    if encoding:
      slideColumn(pix,tpix,x,offset,ysize)
    else:
      slideColumn(pix,tpix,x,-offset,ysize)
  copyPixels(tpix,pix,xsize,ysize)


def plaidify (img):
  tmpimg = Image.new("RGB",img.size)
  shuffleVertical(img,tmpimg,4,True)
  shuffleHorizontal(img,tmpimg,4,True)

def deplaidify (img):
  tmpimg = Image.new("RGB",img.size)
  shuffleHorizontal(img,tmpimg,4,False)
  shuffleVertical(img,tmpimg,4,False)

Результаты

Плед с картинки 1:

пледифицированный 1.jpg

Форма пледа изображение 2:

пледифицированный 2.png


2
Очень хорошо! Можно ли расположить диагонали под углом 45 °?
Тодд Леман

2
Это возможно, изменив линии смещения на: offset = x*xsize/ysize и offset = y*ysize/xsize , к сожалению, это действительно не скрывает изображение.
jrrl

5

Python (+ бонус) - перестановка пикселей

В этом методе каждый пиксель будет размещен в другой позиции с ограничением, что другой пиксель будет размещен в первой позиции. Математически это перестановка с длиной цикла 2. Таким образом, метод является собственным обратным.

Оглядываясь назад, он очень похож на mfvonh, но это представление написано на Python, и мне пришлось самому создавать эту перестановку.

def scramble(I):
    result = np.zeros_like(I)
    size = I.shape[0:2]
    nb_pixels = size[0]*size[1]
    #Build permutation
    np.random.seed(0)
    random_indices = np.random.permutation( range(nb_pixels) )
    random_indices1 = random_indices[0:int(nb_pixels/2)]
    random_indices2 = random_indices[-1:-1-int(nb_pixels/2):-1]
    for c in range(3):
        Ic = I[:,:,c].flatten()
        Ic[ random_indices2 ] = Ic[random_indices1]
        Ic[ random_indices1 ] = I[:,:,c].flatten()[random_indices2]
        result[:,:,c] = Ic.reshape(size)
    return result

Первое тестовое изображение: Первое тестовое изображение Второе тестовое изображение: Второе тестовое изображение


5

Python 2.7 + PIL, вдохновение из скользящих головоломок

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

Результаты:

оригинал

оригинал

Зернистость 16

16

Зернистость 13

13

Зернистость 10

10

Зернистость 3

3

Зернистость 2

2

Зернистость 1

введите описание изображения здесь

оригинал

оригинал

Зернистость 16

16

Зернистость 13

13

Зернистость 10

10

Зернистость 3

3

Зернистость 2

2

Зернистость 1

1

Код:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from PIL import Image
import random

def scramble_blocks(im,granularity,password,nshuffle):
    set_seed(password)
    width=im.size[0]
    height=im.size[1]

    block_width=find_block_dim(granularity,width)       #find the possible block dimensions
    block_height=find_block_dim(granularity,height)

    grid_width_dim=width/block_width                #dimension of the grid
    grid_height_dim=height/block_height

    nblocks=grid_width_dim*grid_height_dim          #number of blocks

    print "nblocks: ",nblocks," block width: ",block_width," block height: ",block_height
    print "image width: ",width," image height: ",height
    print "getting all the blocks ..."
    blocks=[]
    for n in xrange(nblocks): #get all the image blocks
        blocks+=[get_block(im,n,block_width,block_height)]

    print "shuffling ..."
    #shuffle the order of the blocks
    new_order=range(nblocks)
    for n in xrange(nshuffle):
        random.shuffle(new_order)

    print "building final image ..."
    new_image=im.copy()
    for n in xrange(nblocks):
        #define the target box where to paste the new block
        i=(n%grid_width_dim)*block_width                #i,j -> upper left point of the target image
        j=(n/grid_width_dim)*block_height
        box = (i,j,i+block_width,j+block_height)    

        #paste it   
        new_image.paste(blocks[new_order[n]],box)

    return new_image



#find the dimension(height or width) according to the desired granularity (a lower granularity small blocks)
def find_block_dim(granularity,dim):
    assert(granularity>0)
    candidate=0
    block_dim=1
    counter=0
    while counter!=granularity:         #while we dont achive the desired granularity
        candidate+=1
        while((dim%candidate)!=0):      
            candidate+=1
            if candidate>dim:
                counter=granularity-1
                break

        if candidate<=dim:
            block_dim=candidate         #save the current feasible lenght

        counter+=1

    assert(dim%block_dim==0 and block_dim<=dim)
    return block_dim

def unscramble_blocks(im,granularity,password,nshuffle):
    set_seed(password)
    width=im.size[0]
    height=im.size[1]

    block_width=find_block_dim(granularity,width)       #find the possible block dimensions
    block_height=find_block_dim(granularity,height)

    grid_width_dim=width/block_width                #dimension of the grid
    grid_height_dim=height/block_height

    nblocks=grid_width_dim*grid_height_dim          #number of blocks

    print "nblocks: ",nblocks," block width: ",block_width," block height: ",block_height
    print "getting all the blocks ..."
    blocks=[]
    for n in xrange(nblocks): #get all the image blocks
        blocks+=[get_block(im,n,block_width,block_height)]

    print "shuffling ..."
    #shuffle the order of the blocks
    new_order=range(nblocks)
    for n in xrange(nshuffle):
        random.shuffle(new_order)

    print "building final image ..."
    new_image=im.copy()
    for n in xrange(nblocks):
        #define the target box where to paste the new block
        i=(new_order[n]%grid_width_dim)*block_width             #i,j -> upper left point of the target image
        j=(new_order[n]/grid_width_dim)*block_height
        box = (i,j,i+block_width,j+block_height)    

        #paste it   
        new_image.paste(blocks[n],box)

    return new_image

#get a block of the image
def get_block(im,n,block_width,block_height):

    width=im.size[0]

    grid_width_dim=width/block_width                        #dimension of the grid

    i=(n%grid_width_dim)*block_width                        #i,j -> upper left point of the target block
    j=(n/grid_width_dim)*block_height

    box = (i,j,i+block_width,j+block_height)
    block_im = im.crop(box)
    return block_im

#set random seed based on the given password
def set_seed(password):
    passValue=0
    for ch in password:                 
        passValue=passValue+ord(ch)
    random.seed(passValue)


if __name__ == '__main__':

    filename="0RT8s.jpg"
    # filename="B5TbK.png"
    password="yOs0ZaKpiS"
    nshuffle=1
    granularity=1

    im=Image.open(filename)

    new_image=scramble_blocks(im,granularity,password,nshuffle)
    new_image.show()
    new_image.save(filename.split(".")[0]+"_puzzled.png")

    new_image=unscramble_blocks(new_image,granularity,password,nshuffle)
    new_image.save(filename.split(".")[0]+"_unpuzzled.png")
    new_image.show()

5

47

94 строки. 47 для кодирования, 47 для декодирования.

require 'chunky_png'
require_relative 'codegolf-35005_ref.rb'


REF = {:png => ref, :w => 1280, :h => 720}
REF[:pix] = REF[:png].to_rgb_stream.unpack('C*').each_slice(3).to_a
SEVENTH_PRIME = 4*7 - 4-7 - (4&7)
FORTY_SEVEN   = 4*7 + 4+7 + (4&7) + (4^7) + 7/4
THRESHOLD     = FORTY_SEVEN * SEVENTH_PRIME


class RNG
    @@m = 2**32
    @@r = 0.5*(Math.sqrt(5.0) - 1.0)
    def initialize(n=0)
        @cur = FORTY_SEVEN + n
    end
    def hash(seed)
        (@@m*((seed*@@r)%1)).floor
    end
    def _next(max)
        hash(@cur+=1) % max
    end
    def _prev(max)
        hash(@cur-=1) % max
    end        
    def advance(n)
        @cur += n
    end
    def state
        @cur
    end
    alias_method :rand, :_next
end


def load_png(file, resample_w = nil, resample_h = nil)
    png  = ChunkyPNG::Image.from_file(file)
    w    = resample_w || png.width
    h    = resample_h || png.height
    png.resample_nearest_neighbor!(w,h) if resample_w || resample_h
    pix  = png.to_rgb_stream.unpack('C*').each_slice(3).to_a
    return {:png => png, :w => w, :h => h, :pix => pix}
end


def make_png(img)
    rgb_stream = img[:pix].flatten.pack('C*')
    img[:png] = ChunkyPNG::Canvas.from_rgb_stream(img[:w],img[:h],rgb_stream)
    return img
end


def difference(pix_a,pix_b)
    (pix_a[0]+pix_a[1]+pix_a[2]-pix_b[0]-pix_b[1]-pix_b[2]).abs
end


def code(img, img_ref, mode)
    img_in  = load_png(img)
    pix_in  = img_in[:pix]
    pix_ref = img_ref[:pix]
    s = img_in[:w] * img_in[:h] 
    rng = RNG.new(mode==:enc ? 0 : FORTY_SEVEN*s+1)
    rand = mode == :enc ? rng.method(:_next) : rng.method(:_prev)
    s.times do
        FORTY_SEVEN.times do
            j = rand.call(s)
            i = rng.state % s
            diff_val = difference(pix_ref[i],pix_ref[j])
            if diff_val > THRESHOLD
               pix_in[i], pix_in[j] = pix_in[j], pix_in[i]
            end
        end
    end
    make_png(img_in)
end


case ARGV.shift
when 'enc'
    org, cod = ARGV
    encoded_image = code(org,REF,:enc)
    encoded_image[:png].save(cod)
when 'dec'
    org, cod = ARGV
    decoded_image = code(cod,REF,:dec)
    decoded_image[:png].save(org)
else
    puts '<original> <coded>'
    puts 'specify either <enc> or <dec>'
    puts "ruby #{$0} enc codegolf-35005_inp.png codegolf-35005_enc.png"
end

codegolf-35005_ref.rb

введите описание изображения здесь введите описание изображения здесь

(преобразовано в JPG)

введите описание изображения здесь

(оригинал уменьшен)

введите описание изображения здесь


3
Часть оригинального рисунка видна через эти линии. Однако это похоже на то, когда вы рисуете пальцем на запотевшем окне).
Сомниум

2
... + 184426 байт для codegolf-35005_ref.rb?
edc65

5

Matlab с щепоткой теории групп (+ бонус)

В этом подходе мы предполагаем, что у нас есть четное количество всех пикселей. (Если нет, мы просто игнорируем один пиксель). Поэтому нам нужно выбрать половину пикселей, чтобы поменять их с другой половиной. Для этого мы индексируем все пиксели от 0до 2N-1и рассматриваем эти индексы как циклическую группу.

Среди простых чисел мы ищем число p, которое не слишком мало и не слишком велико, и это взаимно просто 2N, порядок нашей группы. Это значит g порождает нашу группу или {k*g mod 2N | k=0,1,...,2N-1} = {0,1,...,2N-1}.

Поэтому мы выбираем первые Nкратные gкак один набор, а все остальные индексы как другой набор и просто меняем соответствующий набор пикселей.

Если pвыбран правильный путь, первый набор равномерно распределяется по всему изображению.

Два теста:

Немного не по теме, но интересно:

Во время тестирования я заметил, что если вы сохраните его в (сжатый с потерями) jpg (вместо сжатого без потерь png) и примените преобразование туда-сюда, вы довольно быстро увидите артефакты сжатия, это покажет результаты двух последовательных перестановок :

Как видите, сжатие jpg делает результат почти черно-белым!

clc;clear;
inputname = 'codegolf_rearrange_pixels2.png';
inputname = 'codegolf_rearrange_pixels2_swapped.png';
outputname = 'codegolf_rearrange_pixels2_swapped.png';

%read image
src = imread(inputname);

%separate into channels
red = src(:,:,1);
green = src(:,:,2);
blue = src(:,:,3);

Ntotal = numel(red(:));  %number of pixels
Nswap = floor(Ntotal/2); %how many pairs we can swap

%find big enough generator
factors = unique(factor(Ntotal));
possible_gen = primes(max(size(red)));
eliminated = setdiff(possible_gen,factors);
if mod(numel(eliminated),2)==0 %make length odd for median
    eliminated = [1,eliminated];
end
generator = median(eliminated);

%set up the swapping vectors
swapindices1 = 1+mod((1:Nswap)*generator, Ntotal);
swapindices2 = setdiff(1:Ntotal,swapindices1);
swapindices2 = swapindices2(1:numel(swapindices1)); %make sure both have the same length

%swap the pixels
red([swapindices1,swapindices2]) = red([swapindices2,swapindices1]);
green([swapindices1,swapindices2]) = green([swapindices2,swapindices1]);
blue([swapindices1,swapindices2]) = blue([swapindices2,swapindices1]);

%write and display
output = cat(3,red,green,blue);
imwrite(output,outputname);
subplot(2,1,1);
imshow(src)
subplot(2,1,2);
imshow(output);

4

JavaScript (+ бонус) - повторитель обмена с делением пикселей

Функция принимает элемент изображения и

  1. Делит пиксели на 8.
  2. Делает обратимый обмен группами пикселей.
  3. Рекурсивная замена, если группа пикселей> = 8.
function E(el){
    var V=document.createElement('canvas')
    var W=V.width=el.width,H=V.height=el.height,C=V.getContext('2d')
    C.drawImage(el,0,0)
    var id=C.getImageData(0,0,W,H),D=id.data,L=D.length,i=L/4,A=[]
    for(;--i;)A[i]=i
    function S(A){
        var L=A.length,x=L>>3,y,t,i=0,s=[]
        if(L<8)return A
        for(;i<L;i+=x)s[i/x]=S(A.slice(i,i+x))
        for(i=4;--i;)y=[6,4,7,5,1,3,0,2][i],t=s[i],s[i]=s[y],s[y]=t
        s=[].concat.apply([],s)
        return s
    }
    var N=C.createImageData(W,H),d=N.data,A=S(A)
    for(var i=0;i<L;i++)d[i]=D[(A[i>>2]*4)+(i%4)]
    C.putImageData(N,0,0)
    el.src=C.canvas.toDataURL()
}

Горы круги


4

Python 2.7 + PIL, колонка / строка скремблер

Этот метод просто скремблирует строки и столбцы изображения. Можно шифровать только одно из измерений или оба. Кроме того, порядок новой зашифрованной строки / столбца основан на пароле. Кроме того, другой возможностью является скремблирование всего массива изображений без учета размеров.

Результаты:

Скремблируем все изображение:

введите описание изображения здесь

введите описание изображения здесь

Скремблирование колонн:

введите описание изображения здесь

введите описание изображения здесь

Скремблирование строк:

введите описание изображения здесь

введите описание изображения здесь

Скремблирование столбцов и строк:

введите описание изображения здесь

введите описание изображения здесь

Я также попытался применить несколько прогонов к изображению, но конечные результаты не сильно отличались, только сложность его расшифровки.

Код:

from PIL import Image
import random,copy

def scramble(im,columns,rows):
    pixels =list(im.getdata())

    newOrder=range(columns*rows)        
    random.shuffle(newOrder)            #shuffle

    newpixels=copy.deepcopy(pixels)
    for i in xrange(len(pixels)):
        newpixels[i]=pixels[newOrder[i]]

    im.putdata(newpixels)

def unscramble(im,columns,rows):
    pixels =list(im.getdata())

    newOrder=range(columns*rows)        
    random.shuffle(newOrder)            #unshuffle

    newpixels=copy.deepcopy(pixels)
    for i in xrange(len(pixels)):
        newpixels[newOrder[i]]=pixels[i]

    im.putdata(newpixels)

def scramble_columns(im,columns,rows):
    pixels =list(im.getdata())

    newOrder=range(columns)     
    random.shuffle(newOrder)            #shuffle

    newpixels=[]
    for i in xrange(rows):
        for j in xrange(columns):
            newpixels+=[pixels[i*columns+newOrder[j]]]

    im.putdata(newpixels)

def unscramble_columns(im,columns,rows):
    pixels =list(im.getdata())

    newOrder=range(columns)     
    random.shuffle(newOrder)            #shuffle

    newpixels=copy.deepcopy(pixels)
    for i in xrange(rows):
        for j in xrange(columns):
            newpixels[i*columns+newOrder[j]]=pixels[i*columns+j]

    im.putdata(newpixels)

def scramble_rows(im,columns,rows):
    pixels =list(im.getdata())

    newOrder=range(rows)        
    random.shuffle(newOrder)            #shuffle the order of pixels

    newpixels=copy.deepcopy(pixels)
    for j in xrange(columns):
        for i in xrange(rows):
            newpixels[i*columns+j]=pixels[columns*newOrder[i]+j]

    im.putdata(newpixels)

def unscramble_rows(im,columns,rows):
    pixels =list(im.getdata())

    newOrder=range(rows)        
    random.shuffle(newOrder)            #shuffle the order of pixels

    newpixels=copy.deepcopy(pixels)
    for j in xrange(columns):
        for i in xrange(rows):
            newpixels[columns*newOrder[i]+j]=pixels[i*columns+j]

    im.putdata(newpixels)


#set random seed based on the given password
def set_seed(password):
    passValue=0
    for ch in password:                 
        passValue=passValue+ord(ch)
    random.seed(passValue)

def encrypt(im,columns,rows,password):
    set_seed(password)
    # scramble(im,columns,rows)
    scramble_columns(im,columns,rows)
    scramble_rows(im,columns,rows)

def decrypt(im,columns,rows,password):
    set_seed(password)
    # unscramble(im,columns,rows)
    unscramble_columns(im,columns,rows)
    unscramble_rows(im,columns,rows)

if __name__ == '__main__':
    passwords=["yOs0ZaKpiS","NA7N3v57og","Nwu2T802mZ","6B2ec75nwu","FP78XHYGmn"]
    iterations=1
    filename="0RT8s.jpg"
    im=Image.open(filename)
    size=im.size
    columns=size[0]
    rows=size[1]

    for i in range(iterations):
        encrypt(im,columns,rows,passwords[i])
    im.save(filename.split(".")[0]+"_encrypted.jpg")

    for i in range(iterations):
        decrypt(im,columns,rows,passwords[iterations-i-1])
    im.save(filename.split(".")[0]+"_decrypted.jpg")

3

C # Winforms

Изображение1: введите описание изображения здесь

Изображение 2: введите описание изображения здесь

Исходный код:

class Program
{
    public static void codec(String src, String trg, bool enc)
    {
        Bitmap bmp = new Bitmap(src);
        Bitmap dst = new Bitmap(bmp.Width, bmp.Height);

        List<Point> points = new List<Point>();
        for (int y = 0; y < bmp.Height; y++)
            for (int x = 0; x < bmp.Width; x++)
                points.Add(new Point(x, y));

        for (int y = 0; y < bmp.Height; y++)
        {
            for (int x = 0; x < bmp.Width; x++)
            {
                int py = Convert.ToInt32(y + 45 * Math.Sin(2.0 * Math.PI * x / 128.0));
                int px = Convert.ToInt32(x + 45 * Math.Sin(2.0 * Math.PI * y / 128.0));

                px = px < 0 ? 0 : px;
                py = py < 0 ? 0 : py;
                px = px >= bmp.Width ? bmp.Width - 1 : px;
                py = py >= bmp.Height ? bmp.Height - 1 : py;

                int srcIndex = x + y * bmp.Width;
                int dstIndex = px + py * bmp.Width;

                Point temp = points[srcIndex];
                points[srcIndex] = points[dstIndex];
                points[dstIndex] = temp;
            }
        }

        for (int y = 0; y < bmp.Height; y++)
        {
            for (int x = 0; x < bmp.Width; x++)
            {
                Point p = points[x + y * bmp.Width];
                if (enc)
                    dst.SetPixel(x, y, bmp.GetPixel(p.X, p.Y));
                else
                    dst.SetPixel(p.X, p.Y, bmp.GetPixel(x, y));
            }
        }

        dst.Save(trg);
    }


    static void Main(string[] args)
    {
        // encode
        codec(@"c:\shared\test.png", @"c:\shared\test_enc.png", true);

        // decode
        codec(@"c:\shared\test_enc.png", @"c:\shared\test_dec.png", false);
    }
}

1

Python 3.6 + pypng

Riffle / Master Shuffle

#!/usr/bin/env python3.6

import argparse
import itertools

import png

def read_image(filename):
    img = png.Reader(filename)
    w, h, data, meta = img.asRGB8()
    return w, h, list(itertools.chain.from_iterable(
        [
            (row[i], row[i+1], row[i+2])
            for i in range(0, len(row), 3)
        ]
        for row in data
    ))

def riffle(img, n=2):
    l = len(img)
    base_size = l // n
    big_groups = l % n
    base_indices = [0]
    for i in range(1, n):
        base_indices.append(base_indices[-1] + base_size + int(i <= big_groups))
    result = []
    for i in range(0, base_size):
        for b in base_indices:
            result.append(img[b + i])
    for i in range(big_groups):
        result.append(img[base_indices[i] + base_size])
    return result

def master(img, n=2):
    parts = [[] for _ in range(n)]
    for i, pixel in enumerate(img):
        parts[i % n].append(pixel)
    return list(itertools.chain.from_iterable(parts))

def main():
    parser = argparse.ArgumentParser()

    parser.add_argument('infile')
    parser.add_argument('outfile')
    parser.add_argument('-r', '--reverse', action='store_true')
    parser.add_argument('-i', '--iterations', type=int, default=1)
    parser.add_argument('-n', '--groupsize', type=int, default=2)
    parser.add_argument('-c', '--complex', nargs='+', type=int)

    args = parser.parse_args()

    w, h, img = read_image(args.infile)

    if args.complex:
        if any(-1 <= n <= 1 for n in args.complex):
            parser.error("Complex keys must use group sizes of at least 2")
        if args.reverse:
            args.complex = [
                -n for n in reversed(args.complex)
            ]
        for n in args.complex:
            if n > 1:
                img = riffle(img, n)
            elif n < -1:
                img = master(img, -n)
    elif args.reverse:
        for _ in range(args.iterations):
            img = master(img, args.groupsize)
    else:
        for _ in range(args.iterations):
            img = riffle(img, args.groupsize)

    writer = png.Writer(w, h)
    with open(args.outfile, 'wb') as f:
        writer.write_array(f, list(itertools.chain.from_iterable(img)))


if __name__ == '__main__':
    main()

Мой алгоритм применяет риффл-тасование в одном направлении и мастер-тасовку в другом (так как оба являются инверсиями друг друга) по несколько итераций в каждой, но каждая обобщается для разделения на любое количество подгрупп, а не только на две. Эффект состоит в том, что вы можете создать многоэтапный ключ перестановки, поскольку изображение не будет восстановлено без знания точной последовательности риффа и мастер-тасов. Последовательность может быть указана с помощью серии целых чисел с положительными числами, представляющими риффы, и отрицательными числами, представляющими мастеров.

Я перетасовал ландшафт ключом [3, -5, 2, 13, -7]:

Пейзаж 3 -5 2 13 -7

Интересно, что некоторые интересные вещи происходят из [3, -5], где остаются некоторые артефакты из исходного изображения:

Пейзаж 3 -5

Вот абстрактный рисунок, перемешанный с ключом [2, 3, 5, 7, -11, 13, -17]:

Круги 2 3 5 7 -11 13 -17

Если в ключе неверен только один параметр, команда unshuffle не восстановит изображение:

Bad Unshuffle

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.